From 96ce4c0ddf38c6655af425cb663bc2aa2fd4a434 Mon Sep 17 00:00:00 2001 From: rbygrave <=> Date: Fri, 14 Sep 2012 00:40:39 +1200 Subject: [PATCH] initial add of EbeanORM server based on v2.8.1 --- .gitignore | 6 + pom.xml | 219 ++ src/assemble/distribution.xml | 19 + .../jdbc/ConnectionDelegator.java | 254 ++ .../jdbc/PreparedStatementDelegator.java | 492 +++ .../jdbc/ConnectionDelegator.java | 340 +++ .../jdbc/PreparedStatementDelegator.java | 634 ++++ .../avaje/ebeaninternal/api/BeanIdList.java | 114 + .../avaje/ebeaninternal/api/BindParams.java | 536 ++++ .../ebeaninternal/api/ClassLoadContext.java | 179 ++ .../avaje/ebeaninternal/api/ClassUtil.java | 111 + .../api/DerivedRelationshipData.java | 27 + .../ebeaninternal/api/HelpScopeTrans.java | 55 + .../ebeaninternal/api/LoadBeanContext.java | 58 + .../ebeaninternal/api/LoadBeanRequest.java | 82 + .../avaje/ebeaninternal/api/LoadContext.java | 79 + .../ebeaninternal/api/LoadManyContext.java | 73 + .../ebeaninternal/api/LoadManyRequest.java | 93 + .../avaje/ebeaninternal/api/LoadRequest.java | 67 + .../ebeaninternal/api/LoadSecondaryQuery.java | 40 + .../ebeaninternal/api/ManyWhereJoins.java | 95 + .../com/avaje/ebeaninternal/api/Monitor.java | 31 + .../avaje/ebeaninternal/api/ScopeTrans.java | 221 ++ .../api/SpiBackgroundExecutor.java | 14 + .../ebeaninternal/api/SpiCallableSql.java | 10 + .../ebeaninternal/api/SpiEbeanServer.java | 210 ++ .../ebeaninternal/api/SpiExpression.java | 70 + .../api/SpiExpressionFactory.java | 32 + .../ebeaninternal/api/SpiExpressionList.java | 71 + .../api/SpiExpressionRequest.java | 52 + .../com/avaje/ebeaninternal/api/SpiQuery.java | 609 ++++ .../avaje/ebeaninternal/api/SpiSqlQuery.java | 77 + .../avaje/ebeaninternal/api/SpiSqlUpdate.java | 8 + .../ebeaninternal/api/SpiTransaction.java | 203 ++ .../api/SpiTransactionScopeManager.java | 6 + .../avaje/ebeaninternal/api/SpiUpdate.java | 76 + .../ebeaninternal/api/SpiUpdatePlan.java | 94 + .../ebeaninternal/api/TransactionEvent.java | 221 ++ .../api/TransactionEventBeans.java | 59 + .../api/TransactionEventTable.java | 143 + .../avaje/ebeaninternal/api/package-info.java | 4 + .../jdbc/ConnectionDelegator.java | 340 +++ .../jdbc/PreparedStatementDelegator.java | 634 ++++ .../server/autofetch/AutoFetchManager.java | 263 ++ .../autofetch/AutoFetchManagerFactory.java | 97 + .../autofetch/DefaultAutoFetchManager.java | 582 ++++ .../DefaultAutoFetchManagerLogging.java | 112 + .../server/autofetch/Statistics.java | 207 ++ .../server/autofetch/StatisticsNodeUsage.java | 123 + .../server/autofetch/StatisticsQuery.java | 42 + .../server/autofetch/TunedQueryInfo.java | 193 ++ .../server/autofetch/package.html | 8 + .../bean/BFAutoFetchStatisticFinder.java | 79 + .../bean/BFAutoFetchTunedFetchFinder.java | 76 + .../server/bean/BFQueryStatisticFinder.java | 69 + .../ebeaninternal/server/bean/package.html | 8 + .../server/cache/CachedBeanData.java | 50 + .../server/cache/CachedBeanDataFromBean.java | 103 + .../server/cache/CachedBeanDataToBean.java | 117 + .../server/cache/CachedBeanDataUpdate.java | 49 + .../server/cache/CachedManyIds.java | 17 + .../server/cache/DefaultCacheHolder.java | 123 + .../server/cache/DefaultServerCache.java | 407 +++ .../cache/DefaultServerCacheFactory.java | 27 + .../cache/DefaultServerCacheManager.java | 112 + .../server/cache/package-info.java | 4 + .../server/cluster/BinaryMessage.java | 83 + .../server/cluster/BinaryMessageList.java | 42 + .../server/cluster/ClusterBroadcast.java | 45 + .../server/cluster/ClusterManager.java | 122 + .../server/cluster/DataHolder.java | 43 + .../ebeaninternal/server/cluster/Packet.java | 212 ++ .../server/cluster/PacketMessages.java | 88 + .../cluster/PacketTransactionEvent.java | 94 + .../server/cluster/PacketWriter.java | 179 ++ .../cluster/SerialiseTransactionHelper.java | 74 + .../cluster/mcast/AckResendMessages.java | 62 + .../cluster/mcast/IncomingPacketsLastAck.java | 72 + .../mcast/IncomingPacketsProcessed.java | 292 ++ .../cluster/mcast/McastClusterManager.java | 614 ++++ .../server/cluster/mcast/McastListener.java | 263 ++ .../cluster/mcast/McastPacketControl.java | 144 + .../server/cluster/mcast/McastSender.java | 135 + .../server/cluster/mcast/McastStatus.java | 155 + .../server/cluster/mcast/Message.java | 33 + .../server/cluster/mcast/MessageAck.java | 76 + .../server/cluster/mcast/MessageControl.java | 92 + .../server/cluster/mcast/MessageResend.java | 96 + .../cluster/mcast/OutgoingPacketsAcked.java | 125 + .../cluster/mcast/OutgoingPacketsCache.java | 85 + .../ebeaninternal/server/cluster/package.html | 12 + .../cluster/socket/RequestProcessor.java | 76 + .../server/cluster/socket/SocketClient.java | 151 + .../socket/SocketClusterBroadcast.java | 265 ++ .../cluster/socket/SocketClusterListener.java | 181 ++ .../cluster/socket/SocketClusterMessage.java | 95 + .../cluster/socket/SocketClusterStatus.java | 60 + .../cluster/socket/SocketConnection.java | 146 + .../server/core/BasicTypeConverter.java | 493 +++ .../server/core/BeanRequest.java | 146 + .../server/core/BootupClassPathSearch.java | 117 + .../server/core/BootupClasses.java | 460 +++ .../server/core/CacheOptions.java | 90 + .../server/core/ConcurrencyMode.java | 22 + .../server/core/ConfigBuilder.java | 25 + .../server/core/DatabasePlatformFactory.java | 191 ++ .../server/core/DebugLazyLoad.java | 111 + .../core/DefaultBackgroundExecutor.java | 71 + .../server/core/DefaultBeanLoader.java | 478 +++ .../server/core/DefaultBeanState.java | 77 + .../server/core/DefaultCallableSql.java | 143 + .../server/core/DefaultServer.java | 2131 +++++++++++++ .../server/core/DefaultServerFactory.java | 518 ++++ .../server/core/DefaultSqlUpdate.java | 246 ++ .../ebeaninternal/server/core/DiffHelp.java | 177 ++ .../server/core/InternString.java | 37 + .../server/core/InternalConfiguration.java | 295 ++ .../server/core/JndiDataSourceLookup.java | 68 + .../ebeaninternal/server/core/Message.java | 83 + .../core/OnBootupClassSearchMatcher.java | 41 + .../server/core/OrmQueryEngine.java | 57 + .../server/core/OrmQueryRequest.java | 429 +++ .../server/core/PersistRequest.java | 127 + .../server/core/PersistRequestBean.java | 708 +++++ .../core/PersistRequestCallableSql.java | 171 ++ .../server/core/PersistRequestOrmUpdate.java | 138 + .../server/core/PersistRequestUpdateSql.java | 145 + .../ebeaninternal/server/core/Persister.java | 107 + .../core/ProxyBeanObjectInputStream.java | 111 + .../ebeaninternal/server/core/PstmtBatch.java | 36 + .../server/core/RefreshHelp.java | 234 ++ .../server/core/RelationalQueryEngine.java | 11 + .../server/core/RelationalQueryRequest.java | 147 + .../server/core/ServletContextListener.java | 76 + .../server/core/SpiOrmQueryRequest.java | 119 + .../core/TraditionalBackgroundExecutor.java | 63 + .../server/core/TransWrapper.java | 46 + .../ebeaninternal/server/core/XmlConfig.java | 83 + .../server/core/XmlConfigLoader.java | 192 ++ .../ebeaninternal/server/core/package.html | 11 + .../server/ddl/AbstractBeanVisitor.java | 65 + .../server/ddl/AbstractPropertyVisitor.java | 41 + .../server/ddl/AddForeignKeysVisitor.java | 149 + .../server/ddl/BaseTablePropertyVisitor.java | 60 + .../ebeaninternal/server/ddl/BeanVisitor.java | 42 + .../server/ddl/CreateIntersectionTable.java | 173 ++ .../server/ddl/CreateSequenceVisitor.java | 64 + .../server/ddl/CreateTableColumnVisitor.java | 223 ++ .../server/ddl/CreateTableVisitor.java | 286 ++ .../server/ddl/DdlGenContext.java | 259 ++ .../server/ddl/DdlGenerator.java | 342 +++ .../server/ddl/DropSequenceVisitor.java | 69 + .../server/ddl/DropTableVisitor.java | 88 + .../server/ddl/PropertyVisitor.java | 53 + .../ebeaninternal/server/ddl/VisitorUtil.java | 125 + .../server/ddl/package-info.java | 4 + .../server/deploy/BeanCascadeInfo.java | 141 + .../server/deploy/BeanCollectionHelp.java | 73 + .../deploy/BeanCollectionHelpFactory.java | 49 + .../server/deploy/BeanDescriptor.java | 2650 +++++++++++++++++ .../server/deploy/BeanDescriptorManager.java | 1566 ++++++++++ .../server/deploy/BeanDescriptorMap.java | 56 + .../server/deploy/BeanEmbeddedMeta.java | 50 + .../deploy/BeanEmbeddedMetaFactory.java | 72 + .../server/deploy/BeanFinderManager.java | 45 + .../server/deploy/BeanFkeyProperty.java | 185 ++ .../server/deploy/BeanForeignKey.java | 75 + .../server/deploy/BeanListHelp.java | 167 ++ .../server/deploy/BeanManager.java | 59 + .../server/deploy/BeanManagerFactory.java | 46 + .../server/deploy/BeanMapHelp.java | 205 ++ .../server/deploy/BeanProperty.java | 1330 +++++++++ .../server/deploy/BeanPropertyAssoc.java | 402 +++ .../server/deploy/BeanPropertyAssocMany.java | 845 ++++++ .../server/deploy/BeanPropertyAssocOne.java | 930 ++++++ .../server/deploy/BeanPropertyCompound.java | 229 ++ .../deploy/BeanPropertyCompoundRoot.java | 129 + .../deploy/BeanPropertyCompoundScalar.java | 120 + .../server/deploy/BeanPropertyOverride.java | 64 + .../deploy/BeanPropertySimpleCollection.java | 99 + .../deploy/BeanQueryAdapterManager.java | 61 + .../server/deploy/BeanSetHelp.java | 174 ++ .../server/deploy/BeanSqlSelect.java | 88 + .../server/deploy/BeanSqlSelectFactory.java | 59 + .../server/deploy/BeanTable.java | 142 + .../deploy/ChainedBeanPersistController.java | 155 + .../deploy/ChainedBeanPersistListener.java | 122 + .../deploy/ChainedBeanQueryAdapter.java | 113 + .../deploy/CollectionTypeConverter.java | 43 + .../deploy/CompoundUniqueContraint.java | 40 + .../server/deploy/CopyContext.java | 83 + .../server/deploy/DNativeQuery.java | 18 + .../server/deploy/DRawSqlColumnInfo.java | 42 + .../server/deploy/DRawSqlMeta.java | 139 + .../server/deploy/DRawSqlSelect.java | 281 ++ .../server/deploy/DRawSqlSelectBuilder.java | 355 +++ .../deploy/DRawSqlSelectColumnsParser.java | 238 ++ .../server/deploy/DbReadContext.java | 96 + .../server/deploy/DbSqlContext.java | 116 + .../deploy/DefaultBeanFinderManager.java | 80 + .../server/deploy/DeployNamedQuery.java | 80 + .../server/deploy/DeployNamedUpdate.java | 40 + .../server/deploy/DeployOrmXml.java | 191 ++ .../server/deploy/DeployParser.java | 183 ++ .../server/deploy/DeployPropertyParser.java | 52 + .../deploy/DeployPropertyParserMap.java | 40 + .../deploy/DeployPropertyParserRawSql.java | 41 + .../server/deploy/DeployUpdateMapFactory.java | 57 + .../server/deploy/DeployUpdateParser.java | 90 + .../server/deploy/DetermineManyType.java | 64 + .../server/deploy/ExportedProperty.java | 71 + .../server/deploy/InheritInfo.java | 371 +++ .../server/deploy/InheritInfoVisitor.java | 13 + .../server/deploy/IntersectionRow.java | 143 + .../ebeaninternal/server/deploy/ManyType.java | 87 + .../server/deploy/ParamTypeUtil.java | 78 + .../deploy/PersistControllerManager.java | 61 + .../server/deploy/PersistListenerManager.java | 85 + .../server/deploy/ReflectGetter.java | 104 + .../server/deploy/ReflectSetter.java | 72 + .../server/deploy/ScalaBufferConverter.java | 48 + .../server/deploy/ScalaMapConverter.java | 48 + .../server/deploy/ScalaSetConverter.java | 49 + .../server/deploy/TableJoin.java | 227 ++ .../server/deploy/TableJoinColumn.java | 86 + .../server/deploy/ebeandeploy.dtd | 16 + .../generatedproperty/CounterFactory.java | 83 + .../generatedproperty/GeneratedCounter.java | 71 + .../GeneratedCounterInteger.java | 66 + .../GeneratedCounterLong.java | 66 + .../GeneratedInsertDate.java | 63 + .../GeneratedInsertLong.java | 61 + .../GeneratedInsertTimestamp.java | 63 + .../generatedproperty/GeneratedProperty.java | 59 + .../GeneratedPropertyFactory.java | 84 + .../GeneratedUpdateDate.java | 64 + .../GeneratedUpdateLong.java | 61 + .../GeneratedUpdateTimestamp.java | 63 + .../InsertTimestampFactory.java | 66 + .../UpdateTimestampFactory.java | 66 + .../deploy/generatedproperty/package.html | 9 + .../server/deploy/id/IdBinder.java | 192 ++ .../server/deploy/id/IdBinderEmbedded.java | 455 +++ .../server/deploy/id/IdBinderEmpty.java | 152 + .../server/deploy/id/IdBinderFactory.java | 59 + .../server/deploy/id/IdBinderMultiple.java | 461 +++ .../server/deploy/id/IdBinderSimple.java | 233 ++ .../server/deploy/id/ImportedId.java | 72 + .../server/deploy/id/ImportedIdEmbedded.java | 164 + .../server/deploy/id/ImportedIdMultiple.java | 123 + .../server/deploy/id/ImportedIdSimple.java | 161 + .../server/deploy/id/package.html | 10 + .../deploy/meta/DeployBeanDescriptor.java | 1013 +++++++ .../deploy/meta/DeployBeanEmbedded.java | 61 + .../deploy/meta/DeployBeanProperty.java | 965 ++++++ .../deploy/meta/DeployBeanPropertyAssoc.java | 175 ++ .../meta/DeployBeanPropertyAssocMany.java | 213 ++ .../meta/DeployBeanPropertyAssocOne.java | 114 + .../meta/DeployBeanPropertyCompound.java | 138 + .../deploy/meta/DeployBeanPropertyLists.java | 403 +++ .../DeployBeanPropertySimpleCollection.java | 60 + .../server/deploy/meta/DeployBeanTable.java | 109 + .../server/deploy/meta/DeployTableJoin.java | 227 ++ .../deploy/meta/DeployTableJoinColumn.java | 197 ++ .../server/deploy/meta/package.html | 10 + .../ebeaninternal/server/deploy/package.html | 14 + .../deploy/parse/AnnotationAssocManys.java | 348 +++ .../deploy/parse/AnnotationAssocOnes.java | 246 ++ .../server/deploy/parse/AnnotationBase.java | 78 + .../deploy/parse/AnnotationBeanTable.java | 50 + .../server/deploy/parse/AnnotationClass.java | 244 ++ .../server/deploy/parse/AnnotationFields.java | 527 ++++ .../server/deploy/parse/AnnotationParser.java | 85 + .../server/deploy/parse/AnnotationSql.java | 61 + .../server/deploy/parse/DeployBeanInfo.java | 102 + .../deploy/parse/DeployCreateProperties.java | 435 +++ .../server/deploy/parse/DeployInherit.java | 196 ++ .../deploy/parse/DeployInheritInfo.java | 283 ++ .../server/deploy/parse/DeployUtil.java | 266 ++ .../server/deploy/parse/DetectScala.java | 62 + .../server/deploy/parse/ReadAnnotations.java | 77 + .../server/deploy/parse/SqlReservedWords.java | 78 + .../deploy/parse/TransientProperties.java | 70 + .../deploy/parse/ValidatorFactoryManager.java | 50 + .../server/deploy/parse/ebeandeploy.dtd | 16 + .../server/deploy/parse/package.html | 11 + .../ebeaninternal/server/el/CharMatch.java | 78 + .../ebeaninternal/server/el/ElComparator.java | 39 + .../server/el/ElComparatorCompound.java | 67 + .../server/el/ElComparatorProperty.java | 70 + .../ebeaninternal/server/el/ElFilter.java | 271 ++ .../server/el/ElMatchBuilder.java | 305 ++ .../ebeaninternal/server/el/ElMatcher.java | 31 + .../server/el/ElPropertyChain.java | 331 ++ .../server/el/ElPropertyChainBuilder.java | 83 + .../server/el/ElPropertyDeploy.java | 68 + .../server/el/ElPropertyValue.java | 140 + .../ebeaninternal/server/el/ElSetValue.java | 29 + .../ebeaninternal/server/el/package-info.java | 4 + .../server/expression/AbstractExpression.java | 76 + .../expression/AllEqualsExpression.java | 133 + .../server/expression/BetweenExpression.java | 48 + .../expression/BetweenPropertyExpression.java | 101 + .../CaseInsensitiveEqualExpression.java | 56 + .../expression/DefaultExampleExpression.java | 249 ++ .../expression/DefaultExpressionFactory.java | 356 +++ .../server/expression/FilterExprPath.java | 61 + .../server/expression/IdExpression.java | 66 + .../server/expression/IdInExpression.java | 86 + .../server/expression/InExpression.java | 100 + .../server/expression/InQueryExpression.java | 86 + .../server/expression/JunctionExpression.java | 421 +++ .../server/expression/LikeExpression.java | 98 + .../server/expression/LogicExpression.java | 92 + .../expression/LuceneAwareExpression.java | 29 + .../server/expression/NotExpression.java | 55 + .../server/expression/NullExpression.java | 58 + .../PersistenceLuceneParseException.java | 35 + .../server/expression/RawExpression.java | 54 + .../server/expression/SimpleExpression.java | 113 + .../server/expression/package.html | 36 + .../server/idgen/UuidIdGenerator.java | 43 + .../ebeaninternal/server/idgen/package.html | 11 + .../server/jdbc/OraclePstmtBatch.java | 140 + .../server/jdbc/StandardPstmtDelegate.java | 42 + .../server/jdbc/package-info.java | 4 + .../server/jmx/MAdminAutofetch.java | 121 + .../server/jmx/MAdminAutofetchMBean.java | 145 + .../server/jmx/MAdminLogging.java | 75 + .../server/jmx/MAdminLoggingMBean.java | 37 + .../server/jmx/package-info.java | 4 + .../server/ldap/DefaultLdapOrmQuery.java | 33 + .../server/ldap/DefaultLdapPersister.java | 160 + .../server/ldap/LdapBeanBuilder.java | 99 + .../server/ldap/LdapOrmQueryEngine.java | 54 + .../server/ldap/LdapOrmQueryExecute.java | 148 + .../server/ldap/LdapOrmQueryRequest.java | 105 + .../server/ldap/LdapPersistBeanRequest.java | 79 + .../server/ldap/LdapPersistenceException.java | 40 + .../server/ldap/LdapQueryDeployHelper.java | 110 + .../ldap/expression/LdAbstractExpression.java | 64 + .../server/ldap/expression/LdEscape.java | 33 + .../ldap/expression/LdIdExpression.java | 70 + .../ldap/expression/LdJunctionExpression.java | 421 +++ .../ldap/expression/LdLikeExpression.java | 57 + .../ldap/expression/LdLogicExpression.java | 92 + .../ldap/expression/LdNotExpression.java | 56 + .../ldap/expression/LdPresentExpression.java | 45 + .../ldap/expression/LdRawExpression.java | 54 + .../ldap/expression/LdSimpleExpression.java | 113 + .../expression/LdapExpressionFactory.java | 268 ++ .../server/ldap/expression/package-info.java | 4 + .../server/ldap/package-info.java | 4 + .../server/lib/BackgroundRunnable.java | 164 + .../server/lib/BackgroundThread.java | 236 ++ .../server/lib/DaemonScheduleThreadPool.java | 97 + .../server/lib/DaemonThreadFactory.java | 68 + .../server/lib/DaemonThreadPool.java | 105 + .../server/lib/ShutdownHook.java | 50 + .../server/lib/ShutdownManager.java | 232 ++ .../ebeaninternal/server/lib/package.html | 13 + .../lib/resource/AbstractResourceSource.java | 56 + .../server/lib/resource/DirectoryFinder.java | 124 + .../lib/resource/FileResourceContent.java | 88 + .../lib/resource/FileResourceSource.java | 68 + .../server/lib/resource/ResourceContent.java | 51 + .../server/lib/resource/ResourceSource.java | 54 + .../lib/resource/UrlResourceContent.java | 94 + .../lib/resource/UrlResourceSource.java | 81 + .../server/lib/resource/package.html | 13 + .../server/lib/sql/BusyConnectionBuffer.java | 155 + .../lib/sql/DataSourceAlertListener.java | 43 + .../server/lib/sql/DataSourceException.java | 41 + .../lib/sql/DataSourceGlobalManager.java | 67 + .../server/lib/sql/DataSourceManager.java | 250 ++ .../server/lib/sql/DataSourceNotify.java | 27 + .../server/lib/sql/DataSourcePool.java | 927 ++++++ .../lib/sql/DataSourcePoolListener.java | 34 + .../lib/sql/ExtendedPreparedStatement.java | 407 +++ .../server/lib/sql/ExtendedStatement.java | 345 +++ .../server/lib/sql/FreeConnectionBuffer.java | 157 + .../server/lib/sql/PooledConnection.java | 961 ++++++ .../server/lib/sql/PooledConnectionQueue.java | 606 ++++ .../ebeaninternal/server/lib/sql/Prefix.java | 115 + .../server/lib/sql/PstmtCache.java | 181 ++ .../server/lib/sql/SimpleAlerter.java | 125 + .../server/lib/sql/TransactionIsolation.java | 86 + .../server/lib/sql/message.properties | 20 + .../ebeaninternal/server/lib/sql/package.html | 12 + .../server/lib/thread/PooledThread.java | 276 ++ .../server/lib/thread/ThreadPool.java | 489 +++ .../server/lib/thread/ThreadPoolManager.java | 194 ++ .../ebeaninternal/server/lib/thread/Work.java | 113 + .../server/lib/thread/package.html | 12 + .../lib/util/CreateObjectException.java | 40 + .../ebeaninternal/server/lib/util/Dnode.java | 345 +++ .../server/lib/util/DnodeParser.java | 219 ++ .../server/lib/util/DnodeReader.java | 87 + .../server/lib/util/GeneralException.java | 39 + .../server/lib/util/InvalidDataException.java | 40 + .../server/lib/util/MailAddress.java | 62 + .../server/lib/util/MailEvent.java | 66 + .../server/lib/util/MailListener.java | 30 + .../server/lib/util/MailMessage.java | 176 ++ .../server/lib/util/MailSender.java | 231 ++ .../server/lib/util/MapFromString.java | 80 + .../server/lib/util/MimeTypeHelper.java | 57 + .../server/lib/util/NotFoundException.java | 40 + .../server/lib/util/StringHelper.java | 616 ++++ .../lib/util/StringParsingException.java | 32 + .../server/lib/util/ThrowablePrinter.java | 108 + .../server/lib/util/mimetypes.properties | 186 ++ .../server/lib/util/package.html | 12 + .../server/loadcontext/DLoadBeanContext.java | 274 ++ .../server/loadcontext/DLoadContext.java | 337 +++ .../server/loadcontext/DLoadHardList.java | 125 + .../server/loadcontext/DLoadList.java | 21 + .../server/loadcontext/DLoadManyContext.java | 180 ++ .../server/loadcontext/DLoadWeakList.java | 134 + .../server/loadcontext/package-info.java | 4 + .../server/persist/BatchControl.java | 269 ++ .../server/persist/BatchDepthComparator.java | 49 + .../server/persist/BatchPostExecute.java | 55 + .../server/persist/BatchedBeanControl.java | 98 + .../server/persist/BatchedBeanHolder.java | 157 + .../server/persist/BatchedPstmt.java | 176 ++ .../server/persist/BatchedPstmtHolder.java | 165 + .../server/persist/BeanPersister.java | 46 + .../server/persist/BeanPersisterFactory.java | 34 + .../server/persist/BindValues.java | 135 + .../ebeaninternal/server/persist/Binder.java | 402 +++ .../server/persist/Constant.java | 46 + .../server/persist/DefaultPersistExecute.java | 154 + .../server/persist/DefaultPersister.java | 1390 +++++++++ .../persist/DeleteUnloadedForeignKeys.java | 111 + .../ebeaninternal/server/persist/DmlUtil.java | 34 + .../server/persist/ExeCallableSql.java | 138 + .../server/persist/ExeOrmUpdate.java | 160 + .../server/persist/ExeUpdateSql.java | 218 ++ .../server/persist/PersistExecute.java | 73 + .../server/persist/PstmtFactory.java | 115 + .../server/persist/dml/DeleteHandler.java | 99 + .../server/persist/dml/DeleteMeta.java | 165 + .../server/persist/dml/DmlBeanPersister.java | 129 + .../persist/dml/DmlBeanPersisterFactory.java | 52 + .../server/persist/dml/DmlHandler.java | 418 +++ .../server/persist/dml/DmlMode.java | 22 + .../persist/dml/GenerateDmlRequest.java | 152 + .../server/persist/dml/InsertHandler.java | 258 ++ .../server/persist/dml/InsertMeta.java | 208 ++ .../server/persist/dml/MetaFactory.java | 146 + .../server/persist/dml/PersistHandler.java | 53 + .../server/persist/dml/UpdateHandler.java | 122 + .../server/persist/dml/UpdateMeta.java | 259 ++ .../server/persist/dml/UpdatePlan.java | 172 ++ .../server/persist/dml/package.html | 10 + .../server/persist/dmlbind/Bindable.java | 75 + .../persist/dmlbind/BindableAssocOne.java | 107 + .../persist/dmlbind/BindableCompound.java | 106 + .../dmlbind/BindableDiscriminator.java | 79 + .../persist/dmlbind/BindableEmbedded.java | 139 + .../dmlbind/BindableEncryptedProperty.java | 144 + .../server/persist/dmlbind/BindableId.java | 61 + .../persist/dmlbind/BindableIdEmbedded.java | 148 + .../persist/dmlbind/BindableIdEmpty.java | 51 + .../server/persist/dmlbind/BindableIdMap.java | 133 + .../persist/dmlbind/BindableIdScalar.java | 108 + .../server/persist/dmlbind/BindableList.java | 80 + .../persist/dmlbind/BindableProperty.java | 102 + .../BindablePropertyInsertGenerated.java | 76 + .../BindablePropertyUpdateGenerated.java | 95 + .../persist/dmlbind/BindableRequest.java | 98 + .../dmlbind/BindableUnidirectional.java | 102 + .../persist/dmlbind/FactoryAssocOnes.java | 68 + .../dmlbind/FactoryBaseProperties.java | 79 + .../persist/dmlbind/FactoryEmbedded.java | 65 + .../server/persist/dmlbind/FactoryId.java | 55 + .../persist/dmlbind/FactoryProperty.java | 84 + .../persist/dmlbind/FactoryVersion.java | 79 + .../dmlbind/MatchedImportedProperty.java | 104 + .../server/persist/dmlbind/package.html | 10 + .../ebeaninternal/server/persist/package.html | 15 + .../server/query/BackgroundFetch.java | 87 + .../server/query/BackgroundIdFetch.java | 128 + .../server/query/BaseFuture.java | 66 + .../server/query/BeanCollectionWrapper.java | 220 ++ .../ebeaninternal/server/query/CQuery.java | 879 ++++++ .../server/query/CQueryBuilder.java | 417 +++ .../server/query/CQueryBuilderRawSql.java | 167 ++ .../server/query/CQueryEngine.java | 417 +++ .../server/query/CQueryFetchIds.java | 335 +++ .../server/query/CQueryIteratorSimple.java | 68 + .../query/CQueryIteratorWithBuffer.java | 89 + .../server/query/CQueryOrderBy.java | 96 + .../server/query/CQueryPlan.java | 172 ++ .../server/query/CQueryPlanRawSql.java | 63 + .../server/query/CQueryPredicates.java | 561 ++++ .../server/query/CQueryRowCount.java | 192 ++ .../server/query/CQueryStats.java | 77 + .../server/query/CallableQuery.java | 47 + .../server/query/CallableQueryIds.java | 51 + .../server/query/CallableQueryList.java | 50 + .../server/query/CallableQueryRowCount.java | 49 + .../server/query/CallableSqlQueryList.java | 56 + .../server/query/CancelableQuery.java | 38 + .../ebeaninternal/server/query/Constants.java | 59 + .../server/query/DefaultDbSqlContext.java | 274 ++ .../server/query/DefaultOrmQueryEngine.java | 148 + .../query/DefaultRelationalQueryEngine.java | 278 ++ .../server/query/DefaultSqlRow.java | 226 ++ .../server/query/LimitOffsetList.java | 244 ++ .../server/query/LimitOffsetPage.java | 127 + .../server/query/LimitOffsetPagingQuery.java | 156 + .../server/query/LoadedPropertiesCache.java | 39 + .../server/query/QueryFutureIds.java | 54 + .../server/query/QueryFutureList.java | 51 + .../server/query/QueryFutureRowCount.java | 49 + .../query/RawSqlSelectClauseBuilder.java | 118 + .../ebeaninternal/server/query/SplitName.java | 63 + .../server/query/SqlBeanLoad.java | 142 + .../server/query/SqlQueryFutureList.java | 34 + .../ebeaninternal/server/query/SqlTree.java | 191 ++ .../server/query/SqlTreeAlias.java | 223 ++ .../server/query/SqlTreeBuilder.java | 683 +++++ .../server/query/SqlTreeNode.java | 55 + .../server/query/SqlTreeNodeBean.java | 541 ++++ .../server/query/SqlTreeNodeExtraJoin.java | 126 + .../server/query/SqlTreeNodeManyRoot.java | 41 + .../query/SqlTreeNodeManyWhereJoin.java | 112 + .../server/query/SqlTreeNodeRoot.java | 55 + .../server/query/SqlTreeProperties.java | 107 + .../server/query/package-info.java | 4 + .../server/querydefn/DefaultOrmQuery.java | 1329 +++++++++ .../server/querydefn/DefaultOrmUpdate.java | 235 ++ .../querydefn/DefaultRelationalQuery.java | 229 ++ .../server/querydefn/NaturalKeyBindParam.java | 22 + .../server/querydefn/OrmQueryDetail.java | 484 +++ .../querydefn/OrmQueryDetailParser.java | 200 ++ .../querydefn/OrmQueryLimitRequest.java | 50 + .../server/querydefn/OrmQueryProperties.java | 642 ++++ .../server/querydefn/SimpleTextParser.java | 177 ++ .../server/querydefn/package-info.java | 4 + .../server/reflect/BeanReflect.java | 52 + .../server/reflect/BeanReflectFactory.java | 33 + .../server/reflect/BeanReflectGetter.java | 34 + .../server/reflect/BeanReflectSetter.java | 40 + .../server/reflect/EnhanceBeanReflect.java | 188 ++ .../reflect/EnhanceBeanReflectFactory.java | 14 + .../ebeaninternal/server/reflect/package.html | 15 + .../server/resource/ResourceManager.java | 48 + .../resource/ResourceManagerFactory.java | 141 + .../server/resource/package.html | 10 + .../server/subclass/GenSuffix.java | 35 + .../server/subclass/GetterSetterMethods.java | 37 + .../server/subclass/MethodWriteReplace.java | 64 + .../server/subclass/SubClassClassAdpater.java | 265 ++ .../server/subclass/SubClassConstructor.java | 56 + .../server/subclass/SubClassFactory.java | 127 + .../server/subclass/SubClassGenerator.java | 272 ++ .../server/subclass/SubClassManager.java | 121 + .../server/subclass/SubClassUtil.java | 47 + .../server/subclass/package.html | 15 + .../server/text/csv/CsvUtilReader.java | 249 ++ .../server/text/csv/TCsvReader.java | 388 +++ .../server/text/csv/package-info.java | 4 + .../server/text/json/DJsonContext.java | 326 ++ .../text/json/DefaultJsonValueAdapter.java | 70 + .../server/text/json/InternalJsonParser.java | 23 + .../server/text/json/PathStack.java | 45 + .../text/json/ReadBasicJsonContext.java | 269 ++ .../server/text/json/ReadJsonContext.java | 201 ++ .../server/text/json/ReadJsonInterface.java | 22 + .../server/text/json/ReadJsonRawReader.java | 126 + .../server/text/json/ReadJsonSource.java | 34 + .../text/json/ReadJsonSourceReader.java | 102 + .../text/json/ReadJsonSourceString.java | 73 + .../server/text/json/WriteJsonBuffer.java | 27 + .../text/json/WriteJsonBufferString.java | 59 + .../text/json/WriteJsonBufferWriter.java | 64 + .../server/text/json/WriteJsonContext.java | 395 +++ .../server/transaction/BeanDelta.java | 121 + .../server/transaction/BeanDeltaList.java | 61 + .../server/transaction/BeanDeltaMap.java | 67 + .../server/transaction/BeanDeltaProperty.java | 54 + .../server/transaction/BeanPathUpdate.java | 43 + .../server/transaction/BeanPathUpdateIds.java | 163 + .../server/transaction/BeanPersistIdMap.java | 69 + .../server/transaction/BeanPersistIds.java | 307 ++ .../transaction/BulkEventListenerMap.java | 62 + .../DefaultPersistenceContext.java | 183 ++ .../DefaultTransactionScopeManager.java | 45 + .../DefaultTransactionThreadLocal.java | 147 + .../server/transaction/DeleteByIdMap.java | 95 + .../transaction/ExternalJdbcTransaction.java | 93 + .../ExternalTransactionScopeManager.java | 67 + .../server/transaction/IndexEvent.java | 68 + .../server/transaction/IndexInvalidate.java | 73 + .../server/transaction/JdbcTransaction.java | 689 +++++ .../server/transaction/JtaTransaction.java | 138 + .../transaction/JtaTransactionManager.java | 249 ++ .../transaction/PostCommitProcessing.java | 202 ++ .../transaction/RemoteTransactionEvent.java | 200 ++ .../transaction/TransactionLogBuffer.java | 121 + .../transaction/TransactionLogManager.java | 58 + .../transaction/TransactionLogWriter.java | 37 + .../transaction/TransactionManager.java | 560 ++++ .../server/transaction/TransactionMap.java | 137 + .../transaction/TransactionScopeManager.java | 58 + .../server/transaction/WeakValueMap.java | 152 + .../log/FileTransactionLogger.java | 401 +++ .../log/FileTransactionLoggerWrapper.java | 101 + .../log/JuliTransactionLogger.java | 67 + .../server/transaction/log/LogTime.java | 192 ++ .../server/transaction/log/SimpleLogger.java | 322 ++ .../server/transaction/log/package.html | 10 + .../server/transaction/package.html | 14 + .../server/type/BeanToDbMap.java | 103 + .../server/type/CtCompoundProperty.java | 101 + .../type/CtCompoundPropertyElAdapter.java | 158 + .../server/type/CtCompoundType.java | 281 ++ .../server/type/CtCompoundTypeScalarList.java | 88 + .../ebeaninternal/server/type/DataBind.java | 147 + .../server/type/DataEncryptSupport.java | 62 + .../ebeaninternal/server/type/DataReader.java | 74 + .../server/type/DefaultTypeFactory.java | 165 + .../server/type/DefaultTypeManager.java | 748 +++++ .../server/type/EnumToDbIntegerMap.java | 56 + .../server/type/EnumToDbStringMap.java | 45 + .../server/type/EnumToDbValueMap.java | 153 + .../ebeaninternal/server/type/EscapeJson.java | 130 + .../type/ImmutableCompoundTypeBuilder.java | 117 + .../server/type/LongToTimestampConverter.java | 43 + .../server/type/RsetDataReader.java | 262 ++ .../server/type/RsetDataReaderIndexed.java | 47 + .../server/type/ScalaOptionTypeConverter.java | 61 + .../server/type/ScalarDataReader.java | 51 + .../ebeaninternal/server/type/ScalarType.java | 204 ++ .../server/type/ScalarTypeBase.java | 101 + .../server/type/ScalarTypeBaseDate.java | 124 + .../server/type/ScalarTypeBaseDateTime.java | 124 + .../server/type/ScalarTypeBaseVarchar.java | 139 + .../server/type/ScalarTypeBigDecimal.java | 97 + .../server/type/ScalarTypeBoolean.java | 310 ++ .../server/type/ScalarTypeByte.java | 97 + .../server/type/ScalarTypeBytesBase.java | 102 + .../server/type/ScalarTypeBytesBinary.java | 38 + .../server/type/ScalarTypeBytesBlob.java | 39 + .../server/type/ScalarTypeBytesEncrypted.java | 137 + .../type/ScalarTypeBytesLongVarbinary.java | 37 + .../server/type/ScalarTypeBytesVarbinary.java | 38 + .../server/type/ScalarTypeCalendar.java | 74 + .../server/type/ScalarTypeChar.java | 92 + .../server/type/ScalarTypeCharArray.java | 92 + .../server/type/ScalarTypeClass.java | 66 + .../server/type/ScalarTypeClob.java | 95 + .../server/type/ScalarTypeCurrency.java | 56 + .../server/type/ScalarTypeDate.java | 67 + .../server/type/ScalarTypeDouble.java | 104 + .../type/ScalarTypeEncryptedWrapper.java | 138 + .../server/type/ScalarTypeEnum.java | 13 + .../server/type/ScalarTypeEnumStandard.java | 281 ++ .../type/ScalarTypeEnumWithMapping.java | 101 + .../server/type/ScalarTypeFloat.java | 103 + .../server/type/ScalarTypeInteger.java | 93 + .../type/ScalarTypeJodaDateMidnight.java | 64 + .../server/type/ScalarTypeJodaDateTime.java | 62 + .../server/type/ScalarTypeJodaLocalDate.java | 66 + .../type/ScalarTypeJodaLocalDateTime.java | 66 + .../server/type/ScalarTypeJodaLocalTime.java | 111 + .../server/type/ScalarTypeLdapBoolean.java | 32 + .../server/type/ScalarTypeLdapDate.java | 160 + .../server/type/ScalarTypeLdapTimestamp.java | 160 + .../server/type/ScalarTypeLocale.java | 83 + .../server/type/ScalarTypeLong.java | 95 + .../type/ScalarTypeLongToTimestamp.java | 29 + .../server/type/ScalarTypeLongVarchar.java | 39 + .../server/type/ScalarTypeMathBigInteger.java | 102 + .../server/type/ScalarTypeScalaDouble.java | 47 + .../server/type/ScalarTypeShort.java | 96 + .../server/type/ScalarTypeString.java | 115 + .../server/type/ScalarTypeTime.java | 98 + .../server/type/ScalarTypeTimeZone.java | 56 + .../server/type/ScalarTypeTimestamp.java | 67 + .../server/type/ScalarTypeURI.java | 61 + .../server/type/ScalarTypeURL.java | 62 + .../server/type/ScalarTypeUUID.java | 66 + .../server/type/ScalarTypeUtilDate.java | 118 + .../server/type/ScalarTypeWrapper.java | 193 ++ .../server/type/SimpleAesEncryptor.java | 147 + .../server/type/TypeManager.java | 79 + .../server/type/TypeReflectHelper.java | 62 + .../ebeaninternal/server/type/package.html | 10 + .../server/type/reflect/CheckImmutable.java | 127 + .../type/reflect/CheckImmutableResponse.java | 41 + .../server/type/reflect/ImmutableMeta.java | 30 + .../type/reflect/ImmutableMetaFactory.java | 246 ++ .../server/type/reflect/KnownImmutable.java | 25 + .../reflect/ReflectionBasedCompoundType.java | 65 + .../ReflectionBasedCompoundTypeProperty.java | 69 + .../ReflectionBasedScalarTypeConverter.java | 68 + .../reflect/ReflectionBasedTypeBuilder.java | 99 + .../server/type/reflect/package-info.java | 4 + .../ebeaninternal/server/util/ArrayStack.java | 107 + .../server/util/BeanCollectionFactory.java | 94 + .../server/util/BeanCollectionParams.java | 45 + .../server/util/BindParamsParser.java | 244 ++ .../server/util/ClassPathReader.java | 19 + .../server/util/ClassPathSearch.java | 323 ++ .../server/util/ClassPathSearchFilter.java | 210 ++ .../server/util/ClassPathSearchMatcher.java | 35 + .../server/util/DefaultClassPathReader.java | 65 + .../server/util/InternalAssert.java | 44 + .../ebeaninternal/server/util/package.html | 12 + .../ebeaninternal/util/CamelCaseHelper.java | 31 + .../util/DefaultExpressionList.java | 501 ++++ .../util/DefaultExpressionRequest.java | 86 + .../util/FilterExpressionList.java | 172 ++ .../ebeaninternal/util/ParamTypeHelper.java | 104 + .../ebeaninternal/util/SortByClause.java | 95 + .../util/SortByClauseParser.java | 90 + .../avaje/ebeaninternal/util/ValueUtil.java | 70 + .../com/avaje/ebeaninternal/util/package.html | 14 + src/main/resources/META-INF/MANIFEST.MF | 3 + .../ebeaninternal/api/message.properties | 81 + src/site/apt/index.apt | 26 + src/site/apt/maven1.apt | 8 + src/site/apt/t1.apt | 1 + src/site/fml/faq.fml | 112 + src/site/resources/css/site.css | 21 + src/site/resources/images/logo-avaje.gif | Bin 0 -> 2341 bytes src/site/resources/images/logo-bg.jpg | Bin 0 -> 871 bytes src/site/site.xml | 64 + src/site/xdoc/l2cache.xml | 313 ++ src/site/xdoc/spring2.xml | 330 ++ .../avaje/ebean/TestLogLevelOrdinalValue.java | 17 + .../com/avaje/ebean/TestRawSqlBuilder.java | 89 + .../ebean/TestRawSqlBuilderDistinct.java | 22 + .../avaje/ebean/TestRawSqlColumnParsing.java | 129 + .../enhance/agent/TestTransformConstruct.java | 25 + .../ebean/server/type/TestTypeManager.java | 71 + .../ebean/text/TestPathPropertiesParse.java | 30 + .../cluster/mcast/TestMcastMemberPackets.java | 58 + .../cluster/mcast/TestPacketsAcked.java | 28 + .../server/lib/sql/TestBusyBuffer.java | 88 + .../server/lib/sql/TestDataSourceMax.java | 85 + .../server/lib/sql/TestFreeBuffer.java | 73 + .../server/loadcontext/TestDLoadWeakList.java | 79 + .../loadcontext/TestDLoadWeakListWithGC.java | 107 + .../server/querydefn/TestQueryLanguage.java | 98 + .../server/rawsql/TestRawSqlParsing.java | 31 + .../MainAutoFetchExcludeLazyLobs.java | 39 + .../tests/autofetch/MainAutoQueryTune1.java | 43 + .../com/avaje/tests/basic/MainDbBoolean.java | 152 + .../com/avaje/tests/basic/MainFutureList.java | 146 + .../basic/MyTestDataSourcePoolListener.java | 29 + .../tests/basic/TestAssocOneEqExpression.java | 42 + .../tests/basic/TestBackgroundFetchAfter.java | 60 + .../com/avaje/tests/basic/TestBatchLazy.java | 41 + .../avaje/tests/basic/TestBatchLazyMany.java | 36 + .../tests/basic/TestBeanReferenceRefresh.java | 42 + .../tests/basic/TestDeleteByIdCollection.java | 74 + .../basic/TestDeleteImportedPartial.java | 38 + .../avaje/tests/basic/TestDeleteOneToOne.java | 49 + .../basic/TestDeleteOneToOneMultiple.java | 33 + .../basic/TestDeletePartialNoVersion.java | 54 + .../avaje/tests/basic/TestDynamicUpdate.java | 46 + .../avaje/tests/basic/TestErrorBindLog.java | 29 + .../avaje/tests/basic/TestExplicitInsert.java | 66 + .../com/avaje/tests/basic/TestFetchId.java | 48 + .../com/avaje/tests/basic/TestIUDVanilla.java | 70 + .../com/avaje/tests/basic/TestInEmpty.java | 25 + .../com/avaje/tests/basic/TestInheritRef.java | 45 + .../com/avaje/tests/basic/TestJodaType.java | 25 + .../tests/basic/TestJoinInheritance.java | 99 + .../basic/TestLazyLoadEmptyOneToMany.java | 37 + .../tests/basic/TestLazyLoadInCache.java | 66 + .../com/avaje/tests/basic/TestLimitQuery.java | 74 + .../avaje/tests/basic/TestLoadBeanCache.java | 32 + .../tests/basic/TestLogTransLogOnError.java | 84 + .../avaje/tests/basic/TestM2MCascadeOne.java | 43 + .../com/avaje/tests/basic/TestM2MVanilla.java | 102 + .../avaje/tests/basic/TestManyLazyLoad.java | 60 + .../avaje/tests/basic/TestMappedSuper.java | 51 + .../tests/basic/TestMultipleOneToOneIUD.java | 59 + .../tests/basic/TestNonUpdateProperty.java | 24 + .../tests/basic/TestOrderByAnnotation.java | 35 + .../basic/TestOrderTotalAmountFormula.java | 37 + .../basic/TestOrderTotalAmountReportBean.java | 54 + .../tests/basic/TestOrderTotalAmountSql.java | 32 + .../com/avaje/tests/basic/TestPaging.java | 186 ++ .../tests/basic/TestPersistenceContext.java | 80 + .../java/com/avaje/tests/basic/TestQuery.java | 58 + .../com/avaje/tests/basic/TestQueryParse.java | 30 + .../tests/basic/TestQueryWhereBetween.java | 32 + .../avaje/tests/basic/TestQueryWithCache.java | 124 + .../tests/basic/TestReadOnlyPropagation.java | 89 + .../tests/basic/TestSaveDeleteOneToOne.java | 36 + .../basic/TestSaveDeleteOneToOneMultiple.java | 36 + .../avaje/tests/basic/TestSerialization.java | 133 + .../basic/TestSharedInstancePropagation.java | 60 + .../com/avaje/tests/basic/TestTransient.java | 61 + .../basic/TestTransientInternalFields.java | 76 + .../tests/basic/TestUpdateManyToOne.java | 40 + .../com/avaje/tests/basic/TestUuidInsert.java | 25 + .../basic/TestUuidInsertMasterDetail.java | 43 + .../basic/TestWeakPersistenceContext.java | 68 + .../tests/basic/TestWhereAnnotation.java | 37 + .../basic/delete/TestDeleteByIdList.java | 39 + .../basic/delete/TestDeleteCascadeById.java | 38 + .../tests/basic/encrypt/BasicEncryptKey.java | 38 + .../basic/encrypt/BasicEncyptKeyManager.java | 20 + .../tests/basic/encrypt/TestEncrypt.java | 103 + .../basic/encrypt/TestEncryptBinary.java | 57 + .../event/MyTestTransactionEventListener.java | 43 + .../basic/event/TestPreInsertValidation.java | 28 + .../tests/basic/event/TestQueryAdapter.java | 34 + .../basic/event/TestTransactionEvent.java | 64 + .../tests/basic/join/TestSecondaryJoin.java | 33 + .../tests/basic/lob/TestBasicClobNoVer.java | 27 + .../avaje/tests/basic/lob/TestByteOnly.java | 30 + .../avaje/tests/basic/one2one/Booking.java | 76 + .../avaje/tests/basic/one2one/Invoice.java | 50 + .../one2one/TestOne2OneBookingInvoice.java | 41 + .../basic/one2one/TestOneToOneWheelTire.java | 19 + .../com/avaje/tests/basic/one2one/Tire.java | 52 + .../com/avaje/tests/basic/one2one/Wheel.java | 50 + .../tests/basic/type/BSimpleWithGen.java | 45 + .../basic/type/TestEnumValueAnnotation.java | 45 + .../basic/type/TestExtraScalarTypes.java | 39 + .../basic/type/TestQueryBooleanProperty.java | 17 + .../tests/basic/type/TestSqlRowUUID.java | 34 + .../tests/basic/type/TestTransientMap.java | 20 + .../tests/basic/vanilla/TestVanillaQuery.java | 71 + .../batchinsert/TestBatchInsertSimple.java | 79 + .../avaje/tests/batchload/TestBasicLazy.java | 254 ++ .../tests/batchload/TestBasicNavOnEmpty.java | 28 + .../batchload/TestBatchLazyWithCacheHits.java | 84 + .../batchload/TestBatchLazyWithDeleted.java | 85 + .../batchload/TestEmptyManyLazyLoad.java | 31 + .../avaje/tests/batchload/TestLazyJoin.java | 41 + .../avaje/tests/batchload/TestLazyJoin2.java | 100 + .../TestLazyLoadEmptyCollection.java | 44 + .../TestLazyLoadNonExistantBean.java | 29 + .../tests/batchload/TestLoadOnDirty.java | 46 + .../avaje/tests/batchload/TestQueryJoin.java | 69 + .../batchload/TestQueryJoinToAssocOne.java | 34 + .../batchload/TestSecondQueryNoRows.java | 27 + .../tests/batchload/TestSecondaryQueries.java | 43 + .../com/avaje/tests/cache/TestCacheBasic.java | 146 + .../tests/cache/TestCacheCollectionIds.java | 87 + .../avaje/tests/cache/TestCacheCustomer.java | 45 + .../avaje/tests/cache/TestCacheNaturalId.java | 72 + .../cache/TestCacheReferenceLazyLoad.java | 33 + .../tests/cache/TestExternalNotification.java | 73 + .../com/avaje/tests/cache/TestQueryCache.java | 68 + .../tests/cache/TestQueryCacheInsert.java | 42 + .../avaje/tests/cascade/TestPrivateOwned.java | 52 + .../TestPrivateOwnedNoCascadeRemove.java | 55 + .../CreateIdExpandedFormServer.java | 30 + .../compositekeys/ModifiedH2Platform.java | 11 + .../tests/compositekeys/TestCKeyDelete.java | 40 + .../compositekeys/TestCKeyIdInExpression.java | 60 + .../tests/compositekeys/TestCKeyLazyLoad.java | 84 + .../avaje/tests/compositekeys/TestCore.java | 162 + .../tests/compositekeys/db/AuditInfo.java | 59 + .../avaje/tests/compositekeys/db/Item.java | 118 + .../avaje/tests/compositekeys/db/ItemKey.java | 63 + .../avaje/tests/compositekeys/db/Parcel.java | 33 + .../compositekeys/db/ParcelLocation.java | 52 + .../avaje/tests/compositekeys/db/Region.java | 51 + .../tests/compositekeys/db/RegionKey.java | 44 + .../avaje/tests/compositekeys/db/SubType.java | 41 + .../tests/compositekeys/db/SubTypeKey.java | 34 + .../avaje/tests/compositekeys/db/Type.java | 62 + .../avaje/tests/compositekeys/db/TypeKey.java | 44 + .../com/avaje/tests/config/TestTableName.java | 46 + .../avaje/tests/ddd/iud/TestDExhEntityEl.java | 78 + .../avaje/tests/ddd/iud/TestDPersonEl.java | 64 + .../avaje/tests/ddd/iud/TestDPersonIUD.java | 64 + .../avaje/tests/ddl/MainDdlWithSchema.java | 28 + .../java/com/avaje/tests/el/TestELBasic.java | 70 + .../avaje/tests/el/TestElGetReference.java | 59 + .../avaje/tests/el/TestUnderscoreParam.java | 25 + .../avaje/tests/genkey/TestGeneratedKeys.java | 113 + .../com/avaje/tests/genkey/TestSeqBatch.java | 34 + .../avaje/tests/idkeys/TestGeneratedKeys.java | 144 + .../com/avaje/tests/idkeys/TestLazyLoad.java | 41 + .../idkeys/TestPropertyChangeSupport.java | 184 ++ .../tests/idkeys/TestSimpleIdInsert.java | 56 + .../com/avaje/tests/idkeys/db/AuditLog.java | 48 + .../avaje/tests/idkeys/db/GenKeyIdentity.java | 36 + .../avaje/tests/idkeys/db/GenKeySequence.java | 40 + .../avaje/tests/idkeys/db/GenKeyTable.java | 36 + .../inheritance/TestDuplcateKeyException.java | 54 + .../tests/inheritance/TestInheritInsert.java | 91 + .../tests/inheritance/TestIntInherit.java | 45 + .../tests/inheritance/TestSkippable.java | 67 + .../company/domain/AbstractBar.java | 61 + .../company/domain/ConcreteBar.java | 12 + .../tests/inheritance/company/domain/Foo.java | 47 + .../company/domain/TestInheritAbstract.java | 57 + .../com/avaje/tests/insert/MainDbCopy.java | 29 + .../com/avaje/tests/iud/TestCarWheelIud.java | 58 + .../tests/iud/TestInsertUpdateTrans.java | 44 + .../com/avaje/tests/ldap/BaseLdapTest.java | 54 + .../avaje/tests/ldap/MockContextFactory.java | 33 + .../com/avaje/tests/ldap/MockDirContext.java | 267 ++ .../tests/ldap/SimpleContextFactory.java | 60 + .../tests/ldap/SimpleLdapSetAdapater.java | 62 + .../avaje/tests/ldap/TestLDPersonDeploy.java | 68 + .../com/avaje/tests/ldap/TestLdapQuery.java | 94 + .../avaje/tests/ldap/TestLdapQueryParse.java | 72 + .../avaje/tests/ldap/TestLdapTimestamp.java | 32 + .../com/avaje/tests/lib/EbeanTestCase.java | 34 + .../java/com/avaje/tests/lib/SimpleTest.java | 13 + .../java/com/avaje/tests/lib/TestMessage.java | 21 + .../tests/m2m/TestM2MDeleteNoCascade.java | 45 + .../tests/m2m/TestM2MDeleteWithCascade.java | 56 + .../avaje/tests/m2m/TestM2MModifyTest.java | 59 + .../com/avaje/tests/model/basic/Address.java | 146 + .../com/avaje/tests/model/basic/Article.java | 66 + .../avaje/tests/model/basic/Attribute.java | 30 + .../tests/model/basic/AttributeHolder.java | 29 + .../avaje/tests/model/basic/BBookmark.java | 74 + .../tests/model/basic/BBookmarkUser.java | 119 + .../avaje/tests/model/basic/BWithQIdent.java | 46 + .../avaje/tests/model/basic/BasicDomain.java | 49 + .../avaje/tests/model/basic/CKeyAssoc.java | 30 + .../avaje/tests/model/basic/CKeyDetail.java | 73 + .../avaje/tests/model/basic/CKeyParent.java | 96 + .../avaje/tests/model/basic/CKeyParentId.java | 71 + .../java/com/avaje/tests/model/basic/Car.java | 51 + .../avaje/tests/model/basic/CarAccessory.java | 31 + .../com/avaje/tests/model/basic/Contact.java | 144 + .../avaje/tests/model/basic/ContactGroup.java | 20 + .../avaje/tests/model/basic/ContactNote.java | 63 + .../com/avaje/tests/model/basic/Country.java | 60 + .../com/avaje/tests/model/basic/Customer.java | 187 ++ .../tests/model/basic/CustomerAggregate.java | 48 + .../com/avaje/tests/model/basic/DMLTest.java | 60 + .../com/avaje/tests/model/basic/EBasic.java | 65 + .../avaje/tests/model/basic/EBasicClob.java | 67 + .../tests/model/basic/EBasicClobNoVer.java | 41 + .../tests/model/basic/EBasicEncrypt.java | 71 + .../model/basic/EBasicEncryptBinary.java | 81 + .../basic/EBasicNoDefaultConstructor.java | 40 + .../avaje/tests/model/basic/EBasicVer.java | 56 + .../model/basic/EBasicWithUniqueCon.java | 78 + .../tests/model/basic/ENullCollection.java | 37 + .../model/basic/ENullCollectionDetail.java | 33 + .../com/avaje/tests/model/basic/EOptOneA.java | 33 + .../com/avaje/tests/model/basic/EOptOneB.java | 32 + .../com/avaje/tests/model/basic/EOptOneC.java | 20 + .../com/avaje/tests/model/basic/ESimple.java | 36 + .../avaje/tests/model/basic/ESomeType.java | 58 + .../avaje/tests/model/basic/ETransMany.java | 44 + .../tests/model/basic/EVanillaCollection.java | 40 + .../model/basic/EVanillaCollectionDetail.java | 33 + .../tests/model/basic/ListAttribute.java | 34 + .../tests/model/basic/ListAttributeValue.java | 34 + .../com/avaje/tests/model/basic/MNonEnum.java | 8 + .../tests/model/basic/MNonUpdPropEntity.java | 57 + .../com/avaje/tests/model/basic/MRole.java | 95 + .../com/avaje/tests/model/basic/MUser.java | 63 + .../com/avaje/tests/model/basic/MnocRole.java | 50 + .../com/avaje/tests/model/basic/MnocUser.java | 73 + .../com/avaje/tests/model/basic/MyAdHoc.java | 33 + .../model/basic/MyEBasicConfigStartup.java | 66 + .../avaje/tests/model/basic/MyLobSize.java | 64 + .../tests/model/basic/MyLobSizeJoinMany.java | 56 + .../tests/model/basic/NoIdEntityType.java | 31 + .../com/avaje/tests/model/basic/OCar.java | 64 + .../com/avaje/tests/model/basic/OEngine.java | 62 + .../com/avaje/tests/model/basic/OGearBox.java | 73 + .../com/avaje/tests/model/basic/Order.java | 270 ++ .../tests/model/basic/OrderAggregate.java | 53 + .../avaje/tests/model/basic/OrderDetail.java | 156 + .../tests/model/basic/OrderShipment.java | 36 + .../com/avaje/tests/model/basic/PFile.java | 55 + .../avaje/tests/model/basic/PFileContent.java | 30 + .../tests/model/basic/PersistentFile.java | 45 + .../model/basic/PersistentFileContent.java | 44 + .../com/avaje/tests/model/basic/Person.java | 67 + .../com/avaje/tests/model/basic/Phone.java | 61 + .../com/avaje/tests/model/basic/Product.java | 109 + .../tests/model/basic/ResetBasicData.java | 312 ++ .../com/avaje/tests/model/basic/Section.java | 59 + .../avaje/tests/model/basic/SomeObject.java | 22 + .../avaje/tests/model/basic/TBytesOnly.java | 35 + .../avaje/tests/model/basic/TIntChild.java | 24 + .../com/avaje/tests/model/basic/TIntRoot.java | 37 + .../avaje/tests/model/basic/TJodaEntity.java | 51 + .../tests/model/basic/TMapSuperEntity.java | 37 + .../tests/model/basic/TMappedSuper2.java | 55 + .../com/avaje/tests/model/basic/TOne.java | 55 + .../com/avaje/tests/model/basic/TSDetail.java | 77 + .../avaje/tests/model/basic/TSDetailTwo.java | 73 + .../com/avaje/tests/model/basic/TSMaster.java | 83 + .../avaje/tests/model/basic/TSMasterTwo.java | 80 + .../avaje/tests/model/basic/TUuidEntity.java | 32 + .../tests/model/basic/TWithPreInsert.java | 43 + .../com/avaje/tests/model/basic/Trip.java | 56 + .../com/avaje/tests/model/basic/Truck.java | 36 + .../com/avaje/tests/model/basic/TruckRef.java | 49 + .../com/avaje/tests/model/basic/UTDetail.java | 77 + .../com/avaje/tests/model/basic/UTMaster.java | 66 + .../com/avaje/tests/model/basic/UUOne.java | 47 + .../com/avaje/tests/model/basic/UUTwo.java | 44 + .../com/avaje/tests/model/basic/Vehicle.java | 46 + .../tests/model/basic/VehicleDriver.java | 55 + .../basic/event/CustomerPersistAdapter.java | 23 + .../basic/event/SimpleTOneQueryAdapter.java | 30 + .../event/TWithPreInsertPersistAdapter.java | 39 + .../model/basic/mapsuper/MapSuperActual.java | 30 + .../model/basic/mapsuper/MapSuperNoId.java | 35 + .../basic/mapsuper/TestMapSuperEquals.java | 37 + .../tests/model/basic/xtra/DummyDao.java | 24 + .../avaje/tests/model/basic/xtra/EdChild.java | 47 + .../model/basic/xtra/EdExtendedParent.java | 21 + .../tests/model/basic/xtra/EdParent.java | 48 + .../OptimisticLockExceptionThrowingDao.java | 48 + .../xtra/TestDeleteUnloadedChildren.java | 96 + .../com/avaje/tests/model/carwheel/Car.java | 52 + .../com/avaje/tests/model/carwheel/Tire.java | 37 + .../com/avaje/tests/model/carwheel/Wheel.java | 64 + .../tests/model/composite/RCustomer.java | 44 + .../tests/model/composite/RCustomerKey.java | 66 + .../avaje/tests/model/composite/ROrder.java | 111 + .../avaje/tests/model/composite/ROrderPK.java | 71 + .../com/avaje/tests/model/ddd/DExhEntity.java | 66 + .../com/avaje/tests/model/ddd/DPerson.java | 83 + .../tests/model/embedded/EEmbDatePeriod.java | 28 + .../avaje/tests/model/embedded/EEmbInner.java | 55 + .../avaje/tests/model/embedded/EEmbOuter.java | 70 + .../com/avaje/tests/model/embedded/EMain.java | 56 + .../tests/model/embedded/Eembeddable.java | 18 + .../com/avaje/tests/model/ivo/CMoney.java | 28 + .../tests/model/ivo/ExhangeCMoneyRate.java | 47 + .../java/com/avaje/tests/model/ivo/Money.java | 142 + .../java/com/avaje/tests/model/ivo/Oid.java | 45 + .../java/com/avaje/tests/model/ivo/Rate.java | 134 + .../com/avaje/tests/model/ivo/Segment.java | 22 + .../tests/model/ivo/SimpleKnownImmutable.java | 49 + .../com/avaje/tests/model/ivo/SysTime.java | 34 + .../tests/model/ivo/TestCheckImmutable.java | 66 + .../ivo/converter/CMoneyCompoundType.java | 53 + .../ivo/converter/ExhangeCompoundType.java | 73 + .../converter/JodaIntervalCompoundType.java | 70 + .../ivo/converter/MoneyTypeConverter.java | 20 + .../model/ivo/converter/OidTypeConverter.java | 22 + .../ivo/converter/RateTypeConverter.java | 20 + .../model/ivo/converter/SysTimeConverter.java | 43 + .../com/avaje/tests/model/ldap/LDPerson.java | 149 + .../java/com/avaje/tests/noid/NoIdEntity.java | 31 + .../java/com/avaje/tests/noid/TestNoId.java | 42 + .../query/TestAutofetchTuneWithJoin.java | 83 + .../query/TestConnectionCloseOnSqlerr.java | 72 + .../tests/query/TestDefaultFetchLazy.java | 52 + .../query/TestExprNestedDisjunction.java | 41 + .../com/avaje/tests/query/TestIContains.java | 43 + .../query/TestInCollectionQueryPlan.java | 36 + .../tests/query/TestJoinOptOneCascade.java | 27 + .../com/avaje/tests/query/TestJoinQuery.java | 40 + .../tests/query/TestLimitAlterFetchMany.java | 35 + .../com/avaje/tests/query/TestLimitQuery.java | 31 + .../avaje/tests/query/TestManyWhereJoin.java | 36 + .../tests/query/TestManyWhereJoinM2M.java | 67 + .../tests/query/TestMyAdHocSqlSelect.java | 27 + .../avaje/tests/query/TestNoSpaceBracket.java | 20 + .../avaje/tests/query/TestPagingListLoop.java | 46 + .../query/TestQueryFetchJoinWithOrder.java | 46 + .../tests/query/TestQueryFetchSkipPath.java | 46 + .../tests/query/TestQueryFilterMany.java | 39 + .../tests/query/TestQueryFilterManyOnM2M.java | 20 + .../query/TestQueryFilterManySimple.java | 40 + .../tests/query/TestQueryFindIterate.java | 45 + .../tests/query/TestQueryFindMapTypedKey.java | 26 + .../tests/query/TestQueryFindReadOnly.java | 51 + .../avaje/tests/query/TestQueryFindVisit.java | 44 + .../tests/query/TestQueryInAssocOne.java | 37 + .../query/TestQueryInIdTypeConversion.java | 29 + .../tests/query/TestQueryJoinManyNonRoot.java | 85 + .../tests/query/TestQueryJoinMulipleMany.java | 36 + .../query/TestQueryJoinQueryNonRoot.java | 58 + .../query/TestQueryLimitOffsetSimple.java | 29 + .../tests/query/TestQueryMultiManyOrder.java | 40 + .../tests/query/TestQueryNullAssocOne.java | 50 + .../avaje/tests/query/TestQueryParsing.java | 64 + .../query/TestQueryPathJoinAndOrder.java | 31 + .../query/TestQueryPlanCacheRowCount.java | 69 + .../tests/query/TestRefToLazyLoadMany.java | 42 + .../com/avaje/tests/query/TestRowCount.java | 39 + .../com/avaje/tests/query/TestSubQuery.java | 206 ++ .../tests/query/TestThreeLevelQueryJoin.java | 27 + .../avaje/tests/query/TestWhereRawClause.java | 24 + .../query/orderby/TestOrderByOnComplex.java | 26 + .../orderby/TestOrderByWithFunction.java | 34 + .../query/orderby/TestOrderByWithMany.java | 119 + .../tests/query/other/TestNoIdEntityType.java | 41 + .../tests/rawsql/TestInsertSqlLogging.java | 24 + .../tests/rawsql/TestOrderReportTotal.java | 33 + .../rawsql/TestRawSqlCustomerAggregate.java | 33 + .../tests/rawsql/TestRawSqlNamedParams.java | 37 + .../tests/rawsql/TestRawSqlOrmQuery.java | 61 + .../rawsql/TestRawSqlOrmQueryDistinct.java | 45 + .../tests/rawsql/TestRawSqlOrmWrapper.java | 80 + .../tests/rawsql/TestRawSqlOrmWrapper2.java | 58 + .../tests/rawsql/TestRawSqlOrmWrapper3.java | 71 + .../rawsql/TestRawSqlPositionedParams.java | 59 + .../rawsql/named/TestRawSqlNamedQuery.java | 23 + .../saveassociation/TestSaveAssociation.java | 35 + .../TestInheritQuery.java | 39 + .../model/PalletLocation.java | 46 + .../model/PalletLocationExternal.java | 21 + .../singleTableInheritance/model/Zone.java | 31 + .../model/ZoneExternal.java | 21 + .../tests/sp/TestManyToManySaveTwice.java | 41 + .../com/avaje/tests/sp/model/IdEntity.java | 57 + .../com/avaje/tests/sp/model/car/Car.java | 31 + .../com/avaje/tests/sp/model/car/Wheel.java | 14 + .../subclass/TestSubclassNewInstance.java | 40 + .../avaje/tests/text/csv/TestCsvReader.java | 47 + .../text/csv/TestCsvReaderWithCallback.java | 58 + .../tests/text/csv/TestTimeStringParser.java | 37 + .../avaje/tests/text/json/TestJsonMap.java | 45 + .../avaje/tests/text/json/TestJsonSimple.java | 74 + .../json/TestTextJsonBeanReadVisitor.java | 83 + ...TextJsonBeanReadVisitorWithCustomJson.java | 107 + .../text/json/TestTextJsonCompoundType.java | 56 + .../text/json/TestTextJsonInheritance.java | 56 + .../text/json/TestTextJsonInsertUpdate.java | 37 + .../text/json/TestTextJsonInvokeLazy.java | 33 + .../json/TestTextJsonReadManyLazyLoad.java | 94 + .../text/json/TestTextJsonReferenceBean.java | 68 + .../tests/text/json/TestTextJsonSimple.java | 56 + .../text/json/TestTextJsonSuperSimple.java | 40 + .../text/json/TestTextJsonUpdateCascade.java | 100 + .../text/json/TestTextJsonUtilDateFormat.java | 66 + .../tests/text/xml/TestDnodeXmlParser.java | 26 + .../transaction/TestInsertManyAndRef.java | 54 + .../avaje/tests/transaction/TestNested.java | 36 + .../transaction/TestPersistContextClear.java | 87 + .../unidirectional/TestUniDirectional.java | 51 + .../avaje/tests/unitinternal/HelloMain.java | 53 + .../avaje/tests/unitinternal/PropsLoader.java | 15 + .../avaje/tests/unitinternal/TestBasics.java | 28 + .../unitinternal/TestEqualsOnEnhanced.java | 43 + .../unitinternal/TestGlobalPropsEval.java | 28 + .../unitinternal/TestGlobalPropsLoader.java | 31 + .../tests/unitinternal/TestLocaleParse.java | 50 + .../unitinternal/TestNamingConvention.java | 55 + .../unitinternal/TestNullCollectionSet.java | 35 + .../tests/unitinternal/TestOrderByParse.java | 118 + .../unitinternal/TestOriginIntEncoding.java | 35 + .../unitinternal/TestSimpleEncryptor.java | 42 + .../TestTxTypeOnTransactional.java | 43 + .../TestVanillaCollectionSet.java | 35 + .../unitinternal/TestXmlConfigLoader.java | 22 + .../update/TestEmbeddedRefreshUpdate.java | 40 + .../tests/update/TestPostgresTruncate.java | 31 + .../tests/update/TestStatelessUpdate.java | 47 + .../avaje/tests/update/TestUpdatePartial.java | 40 + .../tests/update/TestUpdatePartialNoVer.java | 31 + .../avaje/tests/xml/TestXmlSimpleOutput.java | 104 + .../java/com/avaje/tests/xml/XHeader.java | 42 + .../avaje/tests/xml/build/XbAttribute.java | 40 + .../com/avaje/tests/xml/build/XbBase.java | 85 + .../tests/xml/build/XbContextBuilder.java | 86 + .../com/avaje/tests/xml/build/XbNode.java | 163 + .../com/avaje/tests/xml/oxm/OxmContext.java | 36 + .../tests/xml/oxm/OxmContextBuilder.java | 28 + .../tests/xml/oxm/OxmNamingConvention.java | 33 + .../java/com/avaje/tests/xml/oxm/OxmNode.java | 34 + .../avaje/tests/xml/runtime/XoiAttribute.java | 37 + .../com/avaje/tests/xml/runtime/XoiNode.java | 39 + .../avaje/tests/xml/runtime/XrAttribute.java | 86 + .../com/avaje/tests/xml/runtime/XrBase.java | 107 + .../avaje/tests/xml/runtime/XrCollection.java | 195 ++ .../avaje/tests/xml/runtime/XrContext.java | 89 + .../com/avaje/tests/xml/runtime/XrNode.java | 229 ++ .../tests/xml/runtime/XrOutputDocument.java | 51 + .../tests/xml/runtime/XrOutputWriter.java | 67 + .../tests/xml/runtime/XrReadContext.java | 48 + src/test/resources/META-INF/ebean-orm.xml | 67 + src/test/resources/ebean.properties | 134 + src/test/resources/example1.json | 85 + src/test/resources/orm.xml | 40 + src/test/resources/test1.csv | 9 + 1188 files changed, 135034 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/assemble/distribution.xml create mode 100644 src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java create mode 100644 src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java create mode 100644 src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java create mode 100644 src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/BeanIdList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/BindParams.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/ClassLoadContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/DerivedRelationshipData.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/HelpScopeTrans.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/LoadContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/Monitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/ScopeTrans.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiBackgroundExecutor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiCallableSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiSqlQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiTransactionScopeManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/SpiUpdatePlan.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/TransactionEventTable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/api/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsNodeUsage.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/autofetch/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchStatisticFinder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchTunedFetchFinder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/bean/BFQueryStatisticFinder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/bean/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/CachedManyIds.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessage.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessageList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterBroadcast.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/DataHolder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/Packet.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/PacketMessages.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/PacketTransactionEvent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/PacketWriter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/SerialiseTransactionHelper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/AckResendMessages.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsLastAck.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsProcessed.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastClusterManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastPacketControl.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastSender.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastStatus.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/Message.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageAck.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageControl.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageResend.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsAcked.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsCache.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/socket/RequestProcessor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClient.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterBroadcast.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterMessage.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterStatus.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketConnection.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/BasicTypeConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/CacheOptions.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/ConcurrencyMode.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/ConfigBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DatabasePlatformFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DebugLazyLoad.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DefaultBackgroundExecutor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanState.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DefaultCallableSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/InternString.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/JndiDataSourceLookup.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/Message.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/OnBootupClassSearchMatcher.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestCallableSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestOrmUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestUpdateSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/Persister.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/ProxyBeanObjectInputStream.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PstmtBatch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/RefreshHelp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryEngine.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/ServletContextListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/TraditionalBackgroundExecutor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/XmlConfig.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/XmlConfigLoader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractBeanVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractPropertyVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/AddForeignKeysVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/BaseTablePropertyVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/BeanVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/CreateIntersectionTable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/CreateSequenceVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableColumnVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/DropSequenceVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/DropTableVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/PropertyVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/VisitorUtil.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ddl/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMeta.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFinderManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanForeignKey.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManagerFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundRoot.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundScalar.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyOverride.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanQueryAdapterManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelect.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelectFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanQueryAdapter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/CollectionTypeConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/CompoundUniqueContraint.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/CopyContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DNativeQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlColumnInfo.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlMeta.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelect.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectColumnsParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DefaultBeanFinderManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserRawSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateMapFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DetermineManyType.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ExportedProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfo.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfoVisitor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ParamTypeUtil.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/PersistControllerManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/PersistListenerManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectGetter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectSetter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaBufferConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaMapConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaSetConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ebeandeploy.dtd create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/CounterFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterInteger.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterLong.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertDate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertLong.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertTimestamp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateDate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateLong.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateTimestamp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/InsertTimestampFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/UpdateTimestampFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmbedded.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmpty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderMultiple.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderSimple.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedId.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdMultiple.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/id/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanEmbedded.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyCompound.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertySimpleCollection.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanTable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/meta/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInherit.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInheritInfo.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DetectScala.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/SqlReservedWords.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/TransientProperties.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ValidatorFactoryManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ebeandeploy.dtd create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/CharMatch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElComparator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorCompound.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElFilter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElMatchBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElMatcher.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChainBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyDeploy.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/ElSetValue.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/el/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/idgen/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jdbc/OraclePstmtBatch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jdbc/StandardPstmtDelegate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jdbc/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetchMBean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLogging.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLoggingMBean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/jmx/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapOrmQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapPersister.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/LdapBeanBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryEngine.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryExecute.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistBeanRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistenceException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/LdapQueryDeployHelper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdAbstractExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdEscape.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdIdExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdJunctionExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLikeExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLogicExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdNotExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdPresentExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdRawExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdSimpleExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdapExpressionFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/expression/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/ldap/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundRunnable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundThread.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/AbstractResourceSource.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/resource/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceAlertListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceGlobalManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceNotify.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePoolListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleAlerter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/message.properties create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/sql/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/thread/PooledThread.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPool.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPoolManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/thread/Work.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/thread/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/mimetypes.properties create mode 100644 src/main/java/com/avaje/ebeaninternal/server/lib/util/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadHardList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadWeakList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/loadcontext/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlMode.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/GenerateDmlRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dml/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmpty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/BackgroundFetch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/BackgroundIdFetch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryStats.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/Constants.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPage.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagingQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/LoadedPropertiesCache.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlQueryFutureList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultRelationalQuery.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/NaturalKeyBindParam.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/querydefn/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflect.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectGetter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectSetter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflect.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflectFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/reflect/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManagerFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/resource/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/GenSuffix.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/GetterSetterMethods.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/MethodWriteReplace.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassClassAdpater.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassConstructor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassGenerator.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassUtil.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/subclass/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/csv/CsvUtilReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/csv/TCsvReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/csv/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/DefaultJsonValueAdapter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/InternalJsonParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/PathStack.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/ReadBasicJsonContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonInterface.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonRawReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSource.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceString.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBuffer.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferString.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferWriter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDelta.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdateIds.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIdMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/BulkEventListenerMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionThreadLocal.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalJdbcTransaction.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/IndexEvent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/IndexInvalidate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransaction.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransactionManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/RemoteTransactionEvent.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogBuffer.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogWriter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionScopeManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/WeakValueMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLogger.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLoggerWrapper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/log/JuliTransactionLogger.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/log/LogTime.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/log/SimpleLogger.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/log/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/BeanToDbMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundType.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundTypeScalarList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/DataEncryptSupport.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/DataReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/EscapeJson.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ImmutableCompoundTypeBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/LongToTimestampConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalaOptionTypeConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarDataReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBinary.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBlob.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesLongVarbinary.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesVarbinary.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCalendar.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeChar.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCharArray.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClass.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClob.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCurrency.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnum.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateTime.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDateTime.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapBoolean.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapDate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapTimestamp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocale.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongToTimestamp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongVarchar.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeScalaDouble.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTimeZone.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTimestamp.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeURI.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeURL.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUID.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUtilDate.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/SimpleAesEncryptor.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/TypeReflectHelper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/CheckImmutable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/CheckImmutableResponse.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/ImmutableMeta.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/ImmutableMetaFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/KnownImmutable.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/ReflectionBasedCompoundType.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/ReflectionBasedCompoundTypeProperty.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/ReflectionBasedScalarTypeConverter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/ReflectionBasedTypeBuilder.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/reflect/package-info.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/ArrayStack.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/BeanCollectionFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/BeanCollectionParams.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/BindParamsParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/ClassPathReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearch.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearchFilter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearchMatcher.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/DefaultClassPathReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/InternalAssert.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/util/package.html create mode 100644 src/main/java/com/avaje/ebeaninternal/util/CamelCaseHelper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/ParamTypeHelper.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/SortByClause.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/SortByClauseParser.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/ValueUtil.java create mode 100644 src/main/java/com/avaje/ebeaninternal/util/package.html create mode 100644 src/main/resources/META-INF/MANIFEST.MF create mode 100644 src/main/resources/com/avaje/ebeaninternal/api/message.properties create mode 100644 src/site/apt/index.apt create mode 100644 src/site/apt/maven1.apt create mode 100644 src/site/apt/t1.apt create mode 100644 src/site/fml/faq.fml create mode 100644 src/site/resources/css/site.css create mode 100644 src/site/resources/images/logo-avaje.gif create mode 100644 src/site/resources/images/logo-bg.jpg create mode 100644 src/site/site.xml create mode 100644 src/site/xdoc/l2cache.xml create mode 100644 src/site/xdoc/spring2.xml create mode 100644 src/test/java/com/avaje/ebean/TestLogLevelOrdinalValue.java create mode 100644 src/test/java/com/avaje/ebean/TestRawSqlBuilder.java create mode 100644 src/test/java/com/avaje/ebean/TestRawSqlBuilderDistinct.java create mode 100644 src/test/java/com/avaje/ebean/TestRawSqlColumnParsing.java create mode 100644 src/test/java/com/avaje/ebean/enhance/agent/TestTransformConstruct.java create mode 100644 src/test/java/com/avaje/ebean/server/type/TestTypeManager.java create mode 100644 src/test/java/com/avaje/ebean/text/TestPathPropertiesParse.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/cluster/mcast/TestMcastMemberPackets.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/cluster/mcast/TestPacketsAcked.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/lib/sql/TestBusyBuffer.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/lib/sql/TestDataSourceMax.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/lib/sql/TestFreeBuffer.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakList.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakListWithGC.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/rawsql/TestRawSqlParsing.java create mode 100644 src/test/java/com/avaje/tests/autofetch/MainAutoFetchExcludeLazyLobs.java create mode 100644 src/test/java/com/avaje/tests/autofetch/MainAutoQueryTune1.java create mode 100644 src/test/java/com/avaje/tests/basic/MainDbBoolean.java create mode 100644 src/test/java/com/avaje/tests/basic/MainFutureList.java create mode 100644 src/test/java/com/avaje/tests/basic/MyTestDataSourcePoolListener.java create mode 100644 src/test/java/com/avaje/tests/basic/TestAssocOneEqExpression.java create mode 100644 src/test/java/com/avaje/tests/basic/TestBackgroundFetchAfter.java create mode 100644 src/test/java/com/avaje/tests/basic/TestBatchLazy.java create mode 100644 src/test/java/com/avaje/tests/basic/TestBatchLazyMany.java create mode 100644 src/test/java/com/avaje/tests/basic/TestBeanReferenceRefresh.java create mode 100644 src/test/java/com/avaje/tests/basic/TestDeleteByIdCollection.java create mode 100644 src/test/java/com/avaje/tests/basic/TestDeleteImportedPartial.java create mode 100644 src/test/java/com/avaje/tests/basic/TestDeleteOneToOne.java create mode 100644 src/test/java/com/avaje/tests/basic/TestDeleteOneToOneMultiple.java create mode 100644 src/test/java/com/avaje/tests/basic/TestDeletePartialNoVersion.java create mode 100644 src/test/java/com/avaje/tests/basic/TestDynamicUpdate.java create mode 100644 src/test/java/com/avaje/tests/basic/TestErrorBindLog.java create mode 100644 src/test/java/com/avaje/tests/basic/TestExplicitInsert.java create mode 100644 src/test/java/com/avaje/tests/basic/TestFetchId.java create mode 100644 src/test/java/com/avaje/tests/basic/TestIUDVanilla.java create mode 100644 src/test/java/com/avaje/tests/basic/TestInEmpty.java create mode 100644 src/test/java/com/avaje/tests/basic/TestInheritRef.java create mode 100644 src/test/java/com/avaje/tests/basic/TestJodaType.java create mode 100644 src/test/java/com/avaje/tests/basic/TestJoinInheritance.java create mode 100644 src/test/java/com/avaje/tests/basic/TestLazyLoadEmptyOneToMany.java create mode 100644 src/test/java/com/avaje/tests/basic/TestLazyLoadInCache.java create mode 100644 src/test/java/com/avaje/tests/basic/TestLimitQuery.java create mode 100644 src/test/java/com/avaje/tests/basic/TestLoadBeanCache.java create mode 100644 src/test/java/com/avaje/tests/basic/TestLogTransLogOnError.java create mode 100644 src/test/java/com/avaje/tests/basic/TestM2MCascadeOne.java create mode 100644 src/test/java/com/avaje/tests/basic/TestM2MVanilla.java create mode 100644 src/test/java/com/avaje/tests/basic/TestManyLazyLoad.java create mode 100644 src/test/java/com/avaje/tests/basic/TestMappedSuper.java create mode 100644 src/test/java/com/avaje/tests/basic/TestMultipleOneToOneIUD.java create mode 100644 src/test/java/com/avaje/tests/basic/TestNonUpdateProperty.java create mode 100644 src/test/java/com/avaje/tests/basic/TestOrderByAnnotation.java create mode 100644 src/test/java/com/avaje/tests/basic/TestOrderTotalAmountFormula.java create mode 100644 src/test/java/com/avaje/tests/basic/TestOrderTotalAmountReportBean.java create mode 100644 src/test/java/com/avaje/tests/basic/TestOrderTotalAmountSql.java create mode 100644 src/test/java/com/avaje/tests/basic/TestPaging.java create mode 100644 src/test/java/com/avaje/tests/basic/TestPersistenceContext.java create mode 100644 src/test/java/com/avaje/tests/basic/TestQuery.java create mode 100644 src/test/java/com/avaje/tests/basic/TestQueryParse.java create mode 100644 src/test/java/com/avaje/tests/basic/TestQueryWhereBetween.java create mode 100644 src/test/java/com/avaje/tests/basic/TestQueryWithCache.java create mode 100644 src/test/java/com/avaje/tests/basic/TestReadOnlyPropagation.java create mode 100644 src/test/java/com/avaje/tests/basic/TestSaveDeleteOneToOne.java create mode 100644 src/test/java/com/avaje/tests/basic/TestSaveDeleteOneToOneMultiple.java create mode 100644 src/test/java/com/avaje/tests/basic/TestSerialization.java create mode 100644 src/test/java/com/avaje/tests/basic/TestSharedInstancePropagation.java create mode 100644 src/test/java/com/avaje/tests/basic/TestTransient.java create mode 100644 src/test/java/com/avaje/tests/basic/TestTransientInternalFields.java create mode 100644 src/test/java/com/avaje/tests/basic/TestUpdateManyToOne.java create mode 100644 src/test/java/com/avaje/tests/basic/TestUuidInsert.java create mode 100644 src/test/java/com/avaje/tests/basic/TestUuidInsertMasterDetail.java create mode 100644 src/test/java/com/avaje/tests/basic/TestWeakPersistenceContext.java create mode 100644 src/test/java/com/avaje/tests/basic/TestWhereAnnotation.java create mode 100644 src/test/java/com/avaje/tests/basic/delete/TestDeleteByIdList.java create mode 100644 src/test/java/com/avaje/tests/basic/delete/TestDeleteCascadeById.java create mode 100644 src/test/java/com/avaje/tests/basic/encrypt/BasicEncryptKey.java create mode 100644 src/test/java/com/avaje/tests/basic/encrypt/BasicEncyptKeyManager.java create mode 100644 src/test/java/com/avaje/tests/basic/encrypt/TestEncrypt.java create mode 100644 src/test/java/com/avaje/tests/basic/encrypt/TestEncryptBinary.java create mode 100644 src/test/java/com/avaje/tests/basic/event/MyTestTransactionEventListener.java create mode 100644 src/test/java/com/avaje/tests/basic/event/TestPreInsertValidation.java create mode 100644 src/test/java/com/avaje/tests/basic/event/TestQueryAdapter.java create mode 100644 src/test/java/com/avaje/tests/basic/event/TestTransactionEvent.java create mode 100644 src/test/java/com/avaje/tests/basic/join/TestSecondaryJoin.java create mode 100644 src/test/java/com/avaje/tests/basic/lob/TestBasicClobNoVer.java create mode 100644 src/test/java/com/avaje/tests/basic/lob/TestByteOnly.java create mode 100644 src/test/java/com/avaje/tests/basic/one2one/Booking.java create mode 100644 src/test/java/com/avaje/tests/basic/one2one/Invoice.java create mode 100644 src/test/java/com/avaje/tests/basic/one2one/TestOne2OneBookingInvoice.java create mode 100644 src/test/java/com/avaje/tests/basic/one2one/TestOneToOneWheelTire.java create mode 100644 src/test/java/com/avaje/tests/basic/one2one/Tire.java create mode 100644 src/test/java/com/avaje/tests/basic/one2one/Wheel.java create mode 100644 src/test/java/com/avaje/tests/basic/type/BSimpleWithGen.java create mode 100644 src/test/java/com/avaje/tests/basic/type/TestEnumValueAnnotation.java create mode 100644 src/test/java/com/avaje/tests/basic/type/TestExtraScalarTypes.java create mode 100644 src/test/java/com/avaje/tests/basic/type/TestQueryBooleanProperty.java create mode 100644 src/test/java/com/avaje/tests/basic/type/TestSqlRowUUID.java create mode 100644 src/test/java/com/avaje/tests/basic/type/TestTransientMap.java create mode 100644 src/test/java/com/avaje/tests/basic/vanilla/TestVanillaQuery.java create mode 100644 src/test/java/com/avaje/tests/batchinsert/TestBatchInsertSimple.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestBasicLazy.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestBasicNavOnEmpty.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestBatchLazyWithCacheHits.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestBatchLazyWithDeleted.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestEmptyManyLazyLoad.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestLazyJoin.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestLazyJoin2.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestLazyLoadEmptyCollection.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestLazyLoadNonExistantBean.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestLoadOnDirty.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestQueryJoin.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestQueryJoinToAssocOne.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestSecondQueryNoRows.java create mode 100644 src/test/java/com/avaje/tests/batchload/TestSecondaryQueries.java create mode 100644 src/test/java/com/avaje/tests/cache/TestCacheBasic.java create mode 100644 src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java create mode 100644 src/test/java/com/avaje/tests/cache/TestCacheCustomer.java create mode 100644 src/test/java/com/avaje/tests/cache/TestCacheNaturalId.java create mode 100644 src/test/java/com/avaje/tests/cache/TestCacheReferenceLazyLoad.java create mode 100644 src/test/java/com/avaje/tests/cache/TestExternalNotification.java create mode 100644 src/test/java/com/avaje/tests/cache/TestQueryCache.java create mode 100644 src/test/java/com/avaje/tests/cache/TestQueryCacheInsert.java create mode 100644 src/test/java/com/avaje/tests/cascade/TestPrivateOwned.java create mode 100644 src/test/java/com/avaje/tests/cascade/TestPrivateOwnedNoCascadeRemove.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/CreateIdExpandedFormServer.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/ModifiedH2Platform.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/TestCKeyDelete.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/TestCKeyIdInExpression.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/TestCKeyLazyLoad.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/TestCore.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/AuditInfo.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/Item.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/ItemKey.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/Parcel.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/ParcelLocation.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/Region.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/RegionKey.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/SubType.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/SubTypeKey.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/Type.java create mode 100644 src/test/java/com/avaje/tests/compositekeys/db/TypeKey.java create mode 100644 src/test/java/com/avaje/tests/config/TestTableName.java create mode 100644 src/test/java/com/avaje/tests/ddd/iud/TestDExhEntityEl.java create mode 100644 src/test/java/com/avaje/tests/ddd/iud/TestDPersonEl.java create mode 100644 src/test/java/com/avaje/tests/ddd/iud/TestDPersonIUD.java create mode 100644 src/test/java/com/avaje/tests/ddl/MainDdlWithSchema.java create mode 100644 src/test/java/com/avaje/tests/el/TestELBasic.java create mode 100644 src/test/java/com/avaje/tests/el/TestElGetReference.java create mode 100644 src/test/java/com/avaje/tests/el/TestUnderscoreParam.java create mode 100644 src/test/java/com/avaje/tests/genkey/TestGeneratedKeys.java create mode 100644 src/test/java/com/avaje/tests/genkey/TestSeqBatch.java create mode 100644 src/test/java/com/avaje/tests/idkeys/TestGeneratedKeys.java create mode 100644 src/test/java/com/avaje/tests/idkeys/TestLazyLoad.java create mode 100644 src/test/java/com/avaje/tests/idkeys/TestPropertyChangeSupport.java create mode 100644 src/test/java/com/avaje/tests/idkeys/TestSimpleIdInsert.java create mode 100644 src/test/java/com/avaje/tests/idkeys/db/AuditLog.java create mode 100644 src/test/java/com/avaje/tests/idkeys/db/GenKeyIdentity.java create mode 100644 src/test/java/com/avaje/tests/idkeys/db/GenKeySequence.java create mode 100644 src/test/java/com/avaje/tests/idkeys/db/GenKeyTable.java create mode 100644 src/test/java/com/avaje/tests/inheritance/TestDuplcateKeyException.java create mode 100644 src/test/java/com/avaje/tests/inheritance/TestInheritInsert.java create mode 100644 src/test/java/com/avaje/tests/inheritance/TestIntInherit.java create mode 100644 src/test/java/com/avaje/tests/inheritance/TestSkippable.java create mode 100644 src/test/java/com/avaje/tests/inheritance/company/domain/AbstractBar.java create mode 100644 src/test/java/com/avaje/tests/inheritance/company/domain/ConcreteBar.java create mode 100644 src/test/java/com/avaje/tests/inheritance/company/domain/Foo.java create mode 100644 src/test/java/com/avaje/tests/inheritance/company/domain/TestInheritAbstract.java create mode 100644 src/test/java/com/avaje/tests/insert/MainDbCopy.java create mode 100644 src/test/java/com/avaje/tests/iud/TestCarWheelIud.java create mode 100644 src/test/java/com/avaje/tests/iud/TestInsertUpdateTrans.java create mode 100644 src/test/java/com/avaje/tests/ldap/BaseLdapTest.java create mode 100644 src/test/java/com/avaje/tests/ldap/MockContextFactory.java create mode 100644 src/test/java/com/avaje/tests/ldap/MockDirContext.java create mode 100644 src/test/java/com/avaje/tests/ldap/SimpleContextFactory.java create mode 100644 src/test/java/com/avaje/tests/ldap/SimpleLdapSetAdapater.java create mode 100644 src/test/java/com/avaje/tests/ldap/TestLDPersonDeploy.java create mode 100644 src/test/java/com/avaje/tests/ldap/TestLdapQuery.java create mode 100644 src/test/java/com/avaje/tests/ldap/TestLdapQueryParse.java create mode 100644 src/test/java/com/avaje/tests/ldap/TestLdapTimestamp.java create mode 100644 src/test/java/com/avaje/tests/lib/EbeanTestCase.java create mode 100644 src/test/java/com/avaje/tests/lib/SimpleTest.java create mode 100644 src/test/java/com/avaje/tests/lib/TestMessage.java create mode 100644 src/test/java/com/avaje/tests/m2m/TestM2MDeleteNoCascade.java create mode 100644 src/test/java/com/avaje/tests/m2m/TestM2MDeleteWithCascade.java create mode 100644 src/test/java/com/avaje/tests/m2m/TestM2MModifyTest.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Address.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Article.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Attribute.java create mode 100644 src/test/java/com/avaje/tests/model/basic/AttributeHolder.java create mode 100644 src/test/java/com/avaje/tests/model/basic/BBookmark.java create mode 100644 src/test/java/com/avaje/tests/model/basic/BBookmarkUser.java create mode 100644 src/test/java/com/avaje/tests/model/basic/BWithQIdent.java create mode 100644 src/test/java/com/avaje/tests/model/basic/BasicDomain.java create mode 100644 src/test/java/com/avaje/tests/model/basic/CKeyAssoc.java create mode 100644 src/test/java/com/avaje/tests/model/basic/CKeyDetail.java create mode 100644 src/test/java/com/avaje/tests/model/basic/CKeyParent.java create mode 100644 src/test/java/com/avaje/tests/model/basic/CKeyParentId.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Car.java create mode 100644 src/test/java/com/avaje/tests/model/basic/CarAccessory.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Contact.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ContactGroup.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ContactNote.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Country.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Customer.java create mode 100644 src/test/java/com/avaje/tests/model/basic/CustomerAggregate.java create mode 100644 src/test/java/com/avaje/tests/model/basic/DMLTest.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasic.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasicClob.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasicClobNoVer.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasicEncrypt.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasicEncryptBinary.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasicNoDefaultConstructor.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasicVer.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EBasicWithUniqueCon.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ENullCollection.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ENullCollectionDetail.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EOptOneA.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EOptOneB.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EOptOneC.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ESimple.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ESomeType.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ETransMany.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EVanillaCollection.java create mode 100644 src/test/java/com/avaje/tests/model/basic/EVanillaCollectionDetail.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ListAttribute.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ListAttributeValue.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MNonEnum.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MNonUpdPropEntity.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MRole.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MUser.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MnocRole.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MnocUser.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MyAdHoc.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MyEBasicConfigStartup.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MyLobSize.java create mode 100644 src/test/java/com/avaje/tests/model/basic/MyLobSizeJoinMany.java create mode 100644 src/test/java/com/avaje/tests/model/basic/NoIdEntityType.java create mode 100644 src/test/java/com/avaje/tests/model/basic/OCar.java create mode 100644 src/test/java/com/avaje/tests/model/basic/OEngine.java create mode 100644 src/test/java/com/avaje/tests/model/basic/OGearBox.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Order.java create mode 100644 src/test/java/com/avaje/tests/model/basic/OrderAggregate.java create mode 100644 src/test/java/com/avaje/tests/model/basic/OrderDetail.java create mode 100644 src/test/java/com/avaje/tests/model/basic/OrderShipment.java create mode 100644 src/test/java/com/avaje/tests/model/basic/PFile.java create mode 100644 src/test/java/com/avaje/tests/model/basic/PFileContent.java create mode 100644 src/test/java/com/avaje/tests/model/basic/PersistentFile.java create mode 100644 src/test/java/com/avaje/tests/model/basic/PersistentFileContent.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Person.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Phone.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Product.java create mode 100644 src/test/java/com/avaje/tests/model/basic/ResetBasicData.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Section.java create mode 100644 src/test/java/com/avaje/tests/model/basic/SomeObject.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TBytesOnly.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TIntChild.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TIntRoot.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TJodaEntity.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TMapSuperEntity.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TMappedSuper2.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TOne.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TSDetail.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TSDetailTwo.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TSMaster.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TSMasterTwo.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TUuidEntity.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TWithPreInsert.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Trip.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Truck.java create mode 100644 src/test/java/com/avaje/tests/model/basic/TruckRef.java create mode 100644 src/test/java/com/avaje/tests/model/basic/UTDetail.java create mode 100644 src/test/java/com/avaje/tests/model/basic/UTMaster.java create mode 100644 src/test/java/com/avaje/tests/model/basic/UUOne.java create mode 100644 src/test/java/com/avaje/tests/model/basic/UUTwo.java create mode 100644 src/test/java/com/avaje/tests/model/basic/Vehicle.java create mode 100644 src/test/java/com/avaje/tests/model/basic/VehicleDriver.java create mode 100644 src/test/java/com/avaje/tests/model/basic/event/CustomerPersistAdapter.java create mode 100644 src/test/java/com/avaje/tests/model/basic/event/SimpleTOneQueryAdapter.java create mode 100644 src/test/java/com/avaje/tests/model/basic/event/TWithPreInsertPersistAdapter.java create mode 100644 src/test/java/com/avaje/tests/model/basic/mapsuper/MapSuperActual.java create mode 100644 src/test/java/com/avaje/tests/model/basic/mapsuper/MapSuperNoId.java create mode 100644 src/test/java/com/avaje/tests/model/basic/mapsuper/TestMapSuperEquals.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/DummyDao.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/EdChild.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/EdExtendedParent.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/EdParent.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/OptimisticLockExceptionThrowingDao.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/TestDeleteUnloadedChildren.java create mode 100644 src/test/java/com/avaje/tests/model/carwheel/Car.java create mode 100644 src/test/java/com/avaje/tests/model/carwheel/Tire.java create mode 100644 src/test/java/com/avaje/tests/model/carwheel/Wheel.java create mode 100644 src/test/java/com/avaje/tests/model/composite/RCustomer.java create mode 100644 src/test/java/com/avaje/tests/model/composite/RCustomerKey.java create mode 100644 src/test/java/com/avaje/tests/model/composite/ROrder.java create mode 100644 src/test/java/com/avaje/tests/model/composite/ROrderPK.java create mode 100644 src/test/java/com/avaje/tests/model/ddd/DExhEntity.java create mode 100644 src/test/java/com/avaje/tests/model/ddd/DPerson.java create mode 100644 src/test/java/com/avaje/tests/model/embedded/EEmbDatePeriod.java create mode 100644 src/test/java/com/avaje/tests/model/embedded/EEmbInner.java create mode 100644 src/test/java/com/avaje/tests/model/embedded/EEmbOuter.java create mode 100644 src/test/java/com/avaje/tests/model/embedded/EMain.java create mode 100644 src/test/java/com/avaje/tests/model/embedded/Eembeddable.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/CMoney.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/ExhangeCMoneyRate.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/Money.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/Oid.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/Rate.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/Segment.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/SimpleKnownImmutable.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/SysTime.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/TestCheckImmutable.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/converter/CMoneyCompoundType.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/converter/ExhangeCompoundType.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/converter/JodaIntervalCompoundType.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/converter/MoneyTypeConverter.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/converter/OidTypeConverter.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/converter/RateTypeConverter.java create mode 100644 src/test/java/com/avaje/tests/model/ivo/converter/SysTimeConverter.java create mode 100644 src/test/java/com/avaje/tests/model/ldap/LDPerson.java create mode 100644 src/test/java/com/avaje/tests/noid/NoIdEntity.java create mode 100644 src/test/java/com/avaje/tests/noid/TestNoId.java create mode 100644 src/test/java/com/avaje/tests/query/TestAutofetchTuneWithJoin.java create mode 100644 src/test/java/com/avaje/tests/query/TestConnectionCloseOnSqlerr.java create mode 100644 src/test/java/com/avaje/tests/query/TestDefaultFetchLazy.java create mode 100644 src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java create mode 100644 src/test/java/com/avaje/tests/query/TestIContains.java create mode 100644 src/test/java/com/avaje/tests/query/TestInCollectionQueryPlan.java create mode 100644 src/test/java/com/avaje/tests/query/TestJoinOptOneCascade.java create mode 100644 src/test/java/com/avaje/tests/query/TestJoinQuery.java create mode 100644 src/test/java/com/avaje/tests/query/TestLimitAlterFetchMany.java create mode 100644 src/test/java/com/avaje/tests/query/TestLimitQuery.java create mode 100644 src/test/java/com/avaje/tests/query/TestManyWhereJoin.java create mode 100644 src/test/java/com/avaje/tests/query/TestManyWhereJoinM2M.java create mode 100644 src/test/java/com/avaje/tests/query/TestMyAdHocSqlSelect.java create mode 100644 src/test/java/com/avaje/tests/query/TestNoSpaceBracket.java create mode 100644 src/test/java/com/avaje/tests/query/TestPagingListLoop.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFetchJoinWithOrder.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFetchSkipPath.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFilterMany.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFilterManyOnM2M.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFilterManySimple.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFindIterate.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFindMapTypedKey.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFindReadOnly.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryFindVisit.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryInAssocOne.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryInIdTypeConversion.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryJoinManyNonRoot.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryJoinMulipleMany.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryJoinQueryNonRoot.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryLimitOffsetSimple.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryMultiManyOrder.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryNullAssocOne.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryParsing.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryPathJoinAndOrder.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryPlanCacheRowCount.java create mode 100644 src/test/java/com/avaje/tests/query/TestRefToLazyLoadMany.java create mode 100644 src/test/java/com/avaje/tests/query/TestRowCount.java create mode 100644 src/test/java/com/avaje/tests/query/TestSubQuery.java create mode 100644 src/test/java/com/avaje/tests/query/TestThreeLevelQueryJoin.java create mode 100644 src/test/java/com/avaje/tests/query/TestWhereRawClause.java create mode 100644 src/test/java/com/avaje/tests/query/orderby/TestOrderByOnComplex.java create mode 100644 src/test/java/com/avaje/tests/query/orderby/TestOrderByWithFunction.java create mode 100644 src/test/java/com/avaje/tests/query/orderby/TestOrderByWithMany.java create mode 100644 src/test/java/com/avaje/tests/query/other/TestNoIdEntityType.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestInsertSqlLogging.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestOrderReportTotal.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlCustomerAggregate.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlNamedParams.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmQuery.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmQueryDistinct.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmWrapper.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmWrapper2.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmWrapper3.java create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlPositionedParams.java create mode 100644 src/test/java/com/avaje/tests/rawsql/named/TestRawSqlNamedQuery.java create mode 100644 src/test/java/com/avaje/tests/saveassociation/TestSaveAssociation.java create mode 100644 src/test/java/com/avaje/tests/singleTableInheritance/TestInheritQuery.java create mode 100644 src/test/java/com/avaje/tests/singleTableInheritance/model/PalletLocation.java create mode 100644 src/test/java/com/avaje/tests/singleTableInheritance/model/PalletLocationExternal.java create mode 100644 src/test/java/com/avaje/tests/singleTableInheritance/model/Zone.java create mode 100644 src/test/java/com/avaje/tests/singleTableInheritance/model/ZoneExternal.java create mode 100644 src/test/java/com/avaje/tests/sp/TestManyToManySaveTwice.java create mode 100644 src/test/java/com/avaje/tests/sp/model/IdEntity.java create mode 100644 src/test/java/com/avaje/tests/sp/model/car/Car.java create mode 100644 src/test/java/com/avaje/tests/sp/model/car/Wheel.java create mode 100644 src/test/java/com/avaje/tests/subclass/TestSubclassNewInstance.java create mode 100644 src/test/java/com/avaje/tests/text/csv/TestCsvReader.java create mode 100644 src/test/java/com/avaje/tests/text/csv/TestCsvReaderWithCallback.java create mode 100644 src/test/java/com/avaje/tests/text/csv/TestTimeStringParser.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestJsonMap.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestJsonSimple.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonBeanReadVisitor.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonBeanReadVisitorWithCustomJson.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonCompoundType.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonInheritance.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonInsertUpdate.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonInvokeLazy.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonReadManyLazyLoad.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonReferenceBean.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonSimple.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonSuperSimple.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonUpdateCascade.java create mode 100644 src/test/java/com/avaje/tests/text/json/TestTextJsonUtilDateFormat.java create mode 100644 src/test/java/com/avaje/tests/text/xml/TestDnodeXmlParser.java create mode 100644 src/test/java/com/avaje/tests/transaction/TestInsertManyAndRef.java create mode 100644 src/test/java/com/avaje/tests/transaction/TestNested.java create mode 100644 src/test/java/com/avaje/tests/transaction/TestPersistContextClear.java create mode 100644 src/test/java/com/avaje/tests/unidirectional/TestUniDirectional.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/HelloMain.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/PropsLoader.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestBasics.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestEqualsOnEnhanced.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestGlobalPropsEval.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestGlobalPropsLoader.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestLocaleParse.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestNamingConvention.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestNullCollectionSet.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestOrderByParse.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestOriginIntEncoding.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestSimpleEncryptor.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestTxTypeOnTransactional.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestVanillaCollectionSet.java create mode 100644 src/test/java/com/avaje/tests/unitinternal/TestXmlConfigLoader.java create mode 100644 src/test/java/com/avaje/tests/update/TestEmbeddedRefreshUpdate.java create mode 100644 src/test/java/com/avaje/tests/update/TestPostgresTruncate.java create mode 100644 src/test/java/com/avaje/tests/update/TestStatelessUpdate.java create mode 100644 src/test/java/com/avaje/tests/update/TestUpdatePartial.java create mode 100644 src/test/java/com/avaje/tests/update/TestUpdatePartialNoVer.java create mode 100644 src/test/java/com/avaje/tests/xml/TestXmlSimpleOutput.java create mode 100644 src/test/java/com/avaje/tests/xml/XHeader.java create mode 100644 src/test/java/com/avaje/tests/xml/build/XbAttribute.java create mode 100644 src/test/java/com/avaje/tests/xml/build/XbBase.java create mode 100644 src/test/java/com/avaje/tests/xml/build/XbContextBuilder.java create mode 100644 src/test/java/com/avaje/tests/xml/build/XbNode.java create mode 100644 src/test/java/com/avaje/tests/xml/oxm/OxmContext.java create mode 100644 src/test/java/com/avaje/tests/xml/oxm/OxmContextBuilder.java create mode 100644 src/test/java/com/avaje/tests/xml/oxm/OxmNamingConvention.java create mode 100644 src/test/java/com/avaje/tests/xml/oxm/OxmNode.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XoiAttribute.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XoiNode.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrAttribute.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrBase.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrCollection.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrContext.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrNode.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrOutputDocument.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrOutputWriter.java create mode 100644 src/test/java/com/avaje/tests/xml/runtime/XrReadContext.java create mode 100644 src/test/resources/META-INF/ebean-orm.xml create mode 100644 src/test/resources/ebean.properties create mode 100644 src/test/resources/example1.json create mode 100644 src/test/resources/orm.xml create mode 100644 src/test/resources/test1.csv diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..77344b780 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.sql +.classpath +.project +.settings/ +target/ +logs/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..d504f40fc --- /dev/null +++ b/pom.xml @@ -0,0 +1,219 @@ + + 4.0.0 + + + org.avaje + avaje-javaparent + 1.1 + + + org.avaje.ebeanorm + avaje-ebeanorm-server + 3.1.1-SNAPSHOT + jar + + avaje-ebeanorm-server + http://www.avaje.org + + + UTF-8 + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + rbygrave + Rob Bygrave + robin.bygrave@gmail.com + + + + + scm:git:https://github.com/rbygrave/avaje-ebeanorm-server.git + scm:git:https://github.com/rbygrave/avaje-ebeanorm-server.git + https://github.com/rbygrave/avaje-ebeanorm-server.git + + + + + + javax.transaction + jta + 1.1 + provided + + + + javax.servlet + servlet-api + 2.4 + provided + + + + org.avaje.ebeanorm + avaje-ebeanorm-api + [3.1,4) + + + + org.avaje.ebeanorm + avaje-ebeanorm-agent + [3.1,4) + + + + joda-time + joda-time + 1.6 + provided + + + + org.scala-lang + scala-library + 2.10.0-M6 + provided + + + + org.apache.maven + maven-plugin-api + 2.2.1 + jar + provided + + + + junit + junit + 4.4 + test + + + + com.h2database + h2 + 1.3.153 + test + + + + postgresql + postgresql + 8.4-701.jdbc4 + test + + + + org.hsqldb + hsqldb + 2.0.0 + test + + + + mysql + mysql-connector-java + 5.1.15 + test + + + + junit + junit + 4.8.2 + test + + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.7 + + + + enhance-test-classes + process-test-classes + + + + + + + + + UTF-8 + + + run + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.5 + + false + false + + **/Test*.java + **/*Test.java + **/*Tests.java + + + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-antrun-plugin + [1.7,) + + run + + + + + + + + + + + + + + + diff --git a/src/assemble/distribution.xml b/src/assemble/distribution.xml new file mode 100644 index 000000000..231e9979e --- /dev/null +++ b/src/assemble/distribution.xml @@ -0,0 +1,19 @@ + + agent + + jar + + false + + + target/classes + / + + com/avaje/ebean/enhance/** + + + + diff --git a/src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java b/src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java new file mode 100644 index 000000000..efcc387bb --- /dev/null +++ b/src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java @@ -0,0 +1,254 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.jdbc; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.Map; + +public class ConnectionDelegator implements Connection +{ + private final Connection delegate; + + public ConnectionDelegator(Connection delegate) + { + this.delegate = delegate; + } + + public Statement createStatement() + throws SQLException + { + return delegate.createStatement(); + } + + public PreparedStatement prepareStatement(String sql) + throws SQLException + { + return delegate.prepareStatement(sql); + } + + public CallableStatement prepareCall(String sql) + throws SQLException + { + return delegate.prepareCall(sql); + } + + public String nativeSQL(String sql) + throws SQLException + { + return delegate.nativeSQL(sql); + } + + public void setAutoCommit(boolean autoCommit) + throws SQLException + { + delegate.setAutoCommit(autoCommit); + } + + public boolean getAutoCommit() + throws SQLException + { + return delegate.getAutoCommit(); + } + + public void commit() + throws SQLException + { + delegate.commit(); + } + + public void rollback() + throws SQLException + { + delegate.rollback(); + } + + public void close() + throws SQLException + { + delegate.close(); + } + + public boolean isClosed() + throws SQLException + { + return delegate.isClosed(); + } + + public DatabaseMetaData getMetaData() + throws SQLException + { + return delegate.getMetaData(); + } + + public void setReadOnly(boolean readOnly) + throws SQLException + { + delegate.setReadOnly(readOnly); + } + + public boolean isReadOnly() + throws SQLException + { + return delegate.isReadOnly(); + } + + public void setCatalog(String catalog) + throws SQLException + { + delegate.setCatalog(catalog); + } + + public String getCatalog() + throws SQLException + { + return delegate.getCatalog(); + } + + public void setTransactionIsolation(int level) + throws SQLException + { + delegate.setTransactionIsolation(level); + } + + public int getTransactionIsolation() + throws SQLException + { + return delegate.getTransactionIsolation(); + } + + public SQLWarning getWarnings() + throws SQLException + { + return delegate.getWarnings(); + } + + public void clearWarnings() + throws SQLException + { + delegate.clearWarnings(); + } + + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.createStatement(resultSetType, resultSetConcurrency); + } + + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); + } + + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.prepareCall(sql, resultSetType, resultSetConcurrency); + } + + public Map> getTypeMap() + throws SQLException + { + return delegate.getTypeMap(); + } + + public void setTypeMap(Map> map) + throws SQLException + { + delegate.setTypeMap(map); + } + + public void setHoldability(int holdability) + throws SQLException + { + delegate.setHoldability(holdability); + } + + public int getHoldability() + throws SQLException + { + return delegate.getHoldability(); + } + + public Savepoint setSavepoint() + throws SQLException + { + return delegate.setSavepoint(); + } + + public Savepoint setSavepoint(String name) + throws SQLException + { + return delegate.setSavepoint(name); + } + + public void rollback(Savepoint savepoint) + throws SQLException + { + delegate.rollback(savepoint); + } + + public void releaseSavepoint(Savepoint savepoint) + throws SQLException + { + delegate.releaseSavepoint(savepoint); + } + + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.prepareStatement(sql, autoGeneratedKeys); + } + + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.prepareStatement(sql, columnIndexes); + } + + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException + { + return delegate.prepareStatement(sql, columnNames); + } +} diff --git a/src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java b/src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java new file mode 100644 index 000000000..e3a401332 --- /dev/null +++ b/src/jdk_1.5/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java @@ -0,0 +1,492 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; + +public class PreparedStatementDelegator implements PreparedStatement +{ + private final PreparedStatement delegate; + + public PreparedStatementDelegator(PreparedStatement delegate) + { + this.delegate = delegate; + } + + public ResultSet executeQuery() + throws SQLException + { + return delegate.executeQuery(); + } + + public int executeUpdate() + throws SQLException + { + return delegate.executeUpdate(); + } + + public void setNull(int parameterIndex, int sqlType) + throws SQLException + { + delegate.setNull(parameterIndex, sqlType); + } + + public void setBoolean(int parameterIndex, boolean x) + throws SQLException + { + delegate.setBoolean(parameterIndex, x); + } + + public void setByte(int parameterIndex, byte x) + throws SQLException + { + delegate.setByte(parameterIndex, x); + } + + public void setShort(int parameterIndex, short x) + throws SQLException + { + delegate.setShort(parameterIndex, x); + } + + public void setInt(int parameterIndex, int x) + throws SQLException + { + delegate.setInt(parameterIndex, x); + } + + public void setLong(int parameterIndex, long x) + throws SQLException + { + delegate.setLong(parameterIndex, x); + } + + public void setFloat(int parameterIndex, float x) + throws SQLException + { + delegate.setFloat(parameterIndex, x); + } + + public void setDouble(int parameterIndex, double x) + throws SQLException + { + delegate.setDouble(parameterIndex, x); + } + + public void setBigDecimal(int parameterIndex, BigDecimal x) + throws SQLException + { + delegate.setBigDecimal(parameterIndex, x); + } + + public void setString(int parameterIndex, String x) + throws SQLException + { + delegate.setString(parameterIndex, x); + } + + public void setBytes(int parameterIndex, byte[] x) + throws SQLException + { + delegate.setBytes(parameterIndex, x); + } + + public void setDate(int parameterIndex, Date x) + throws SQLException + { + delegate.setDate(parameterIndex, x); + } + + public void setTime(int parameterIndex, Time x) + throws SQLException + { + delegate.setTime(parameterIndex, x); + } + + public void setTimestamp(int parameterIndex, Timestamp x) + throws SQLException + { + delegate.setTimestamp(parameterIndex, x); + } + + public void setAsciiStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setAsciiStream(parameterIndex, x, length); + } + + @SuppressWarnings("deprecation") + public void setUnicodeStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setUnicodeStream(parameterIndex, x, length); + } + + public void setBinaryStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setBinaryStream(parameterIndex, x, length); + } + + public void clearParameters() + throws SQLException + { + delegate.clearParameters(); + } + + public void setObject(int i, Object o, int i1, int i2) throws SQLException + { + delegate.setObject(i, o, i1, i2); + } + + public void setObject(int parameterIndex, Object x, int targetSqlType) + throws SQLException + { + delegate.setObject(parameterIndex, x, targetSqlType); + } + + public void setObject(int parameterIndex, Object x) + throws SQLException + { + delegate.setObject(parameterIndex, x); + } + + public boolean execute() + throws SQLException + { + return delegate.execute(); + } + + public void addBatch() + throws SQLException + { + delegate.addBatch(); + } + + public void setCharacterStream(int parameterIndex, Reader reader, int length) + throws SQLException + { + delegate.setCharacterStream(parameterIndex, reader, length); + } + + public void setRef(int parameterIndex, Ref x) + throws SQLException + { + delegate.setRef(parameterIndex, x); + } + + public void setBlob(int parameterIndex, Blob x) + throws SQLException + { + delegate.setBlob(parameterIndex, x); + } + + public void setClob(int parameterIndex, Clob x) + throws SQLException + { + delegate.setClob(parameterIndex, x); + } + + public void setArray(int parameterIndex, Array x) + throws SQLException + { + delegate.setArray(parameterIndex, x); + } + + public ResultSetMetaData getMetaData() + throws SQLException + { + return delegate.getMetaData(); + } + + public void setDate(int parameterIndex, Date x, Calendar cal) + throws SQLException + { + delegate.setDate(parameterIndex, x, cal); + } + + public void setTime(int parameterIndex, Time x, Calendar cal) + throws SQLException + { + delegate.setTime(parameterIndex, x, cal); + } + + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) + throws SQLException + { + delegate.setTimestamp(parameterIndex, x, cal); + } + + public void setNull(int parameterIndex, int sqlType, String typeName) + throws SQLException + { + delegate.setNull(parameterIndex, sqlType, typeName); + } + + public void setURL(int parameterIndex, URL x) + throws SQLException + { + delegate.setURL(parameterIndex, x); + } + + public ParameterMetaData getParameterMetaData() + throws SQLException + { + return delegate.getParameterMetaData(); + } + + public ResultSet executeQuery(String sql) + throws SQLException + { + return delegate.executeQuery(sql); + } + + public int executeUpdate(String sql) + throws SQLException + { + return delegate.executeUpdate(sql); + } + + public void close() + throws SQLException + { + delegate.close(); + } + + public int getMaxFieldSize() + throws SQLException + { + return delegate.getMaxFieldSize(); + } + + public void setMaxFieldSize(int max) + throws SQLException + { + delegate.setMaxFieldSize(max); + } + + public int getMaxRows() + throws SQLException + { + return delegate.getMaxRows(); + } + + public void setMaxRows(int max) + throws SQLException + { + delegate.setMaxRows(max); + } + + public void setEscapeProcessing(boolean enable) + throws SQLException + { + delegate.setEscapeProcessing(enable); + } + + public int getQueryTimeout() + throws SQLException + { + return delegate.getQueryTimeout(); + } + + public void setQueryTimeout(int seconds) + throws SQLException + { + delegate.setQueryTimeout(seconds); + } + + public void cancel() + throws SQLException + { + delegate.cancel(); + } + + public SQLWarning getWarnings() + throws SQLException + { + return delegate.getWarnings(); + } + + public void clearWarnings() + throws SQLException + { + delegate.clearWarnings(); + } + + public void setCursorName(String name) + throws SQLException + { + delegate.setCursorName(name); + } + + public boolean execute(String sql) + throws SQLException + { + return delegate.execute(sql); + } + + public ResultSet getResultSet() + throws SQLException + { + return delegate.getResultSet(); + } + + public int getUpdateCount() + throws SQLException + { + return delegate.getUpdateCount(); + } + + public boolean getMoreResults() + throws SQLException + { + return delegate.getMoreResults(); + } + + public void setFetchDirection(int direction) + throws SQLException + { + delegate.setFetchDirection(direction); + } + + public int getFetchDirection() + throws SQLException + { + return delegate.getFetchDirection(); + } + + public void setFetchSize(int rows) + throws SQLException + { + delegate.setFetchSize(rows); + } + + public int getFetchSize() + throws SQLException + { + return delegate.getFetchSize(); + } + + public int getResultSetConcurrency() + throws SQLException + { + return delegate.getResultSetConcurrency(); + } + + public int getResultSetType() + throws SQLException + { + return delegate.getResultSetType(); + } + + public void addBatch(String sql) + throws SQLException + { + delegate.addBatch(sql); + } + + public void clearBatch() + throws SQLException + { + delegate.clearBatch(); + } + + public int[] executeBatch() + throws SQLException + { + return delegate.executeBatch(); + } + + public Connection getConnection() + throws SQLException + { + return delegate.getConnection(); + } + + public boolean getMoreResults(int current) + throws SQLException + { + return delegate.getMoreResults(current); + } + + public ResultSet getGeneratedKeys() + throws SQLException + { + return delegate.getGeneratedKeys(); + } + + public int executeUpdate(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.executeUpdate(sql, autoGeneratedKeys); + } + + public int executeUpdate(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.executeUpdate(sql, columnIndexes); + } + + public int executeUpdate(String sql, String[] columnNames) + throws SQLException + { + return delegate.executeUpdate(sql, columnNames); + } + + public boolean execute(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.execute(sql, autoGeneratedKeys); + } + + public boolean execute(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.execute(sql, columnIndexes); + } + + public boolean execute(String sql, String[] columnNames) + throws SQLException + { + return delegate.execute(sql, columnNames); + } + + public int getResultSetHoldability() + throws SQLException + { + return delegate.getResultSetHoldability(); + } +} diff --git a/src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java b/src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java new file mode 100644 index 000000000..51c6e47e0 --- /dev/null +++ b/src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java @@ -0,0 +1,340 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.jdbc; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; + +public class ConnectionDelegator implements Connection +{ + private final Connection delegate; + + public ConnectionDelegator(Connection delegate) + { + this.delegate = delegate; + } + + public Statement createStatement() + throws SQLException + { + return delegate.createStatement(); + } + + public PreparedStatement prepareStatement(String sql) + throws SQLException + { + return delegate.prepareStatement(sql); + } + + public CallableStatement prepareCall(String sql) + throws SQLException + { + return delegate.prepareCall(sql); + } + + public String nativeSQL(String sql) + throws SQLException + { + return delegate.nativeSQL(sql); + } + + public void setAutoCommit(boolean autoCommit) + throws SQLException + { + delegate.setAutoCommit(autoCommit); + } + + public boolean getAutoCommit() + throws SQLException + { + return delegate.getAutoCommit(); + } + + public void commit() + throws SQLException + { + delegate.commit(); + } + + public void rollback() + throws SQLException + { + delegate.rollback(); + } + + public void close() + throws SQLException + { + delegate.close(); + } + + public boolean isClosed() + throws SQLException + { + return delegate.isClosed(); + } + + public DatabaseMetaData getMetaData() + throws SQLException + { + return delegate.getMetaData(); + } + + public void setReadOnly(boolean readOnly) + throws SQLException + { + delegate.setReadOnly(readOnly); + } + + public boolean isReadOnly() + throws SQLException + { + return delegate.isReadOnly(); + } + + public void setCatalog(String catalog) + throws SQLException + { + delegate.setCatalog(catalog); + } + + public String getCatalog() + throws SQLException + { + return delegate.getCatalog(); + } + + public void setTransactionIsolation(int level) + throws SQLException + { + delegate.setTransactionIsolation(level); + } + + public int getTransactionIsolation() + throws SQLException + { + return delegate.getTransactionIsolation(); + } + + public SQLWarning getWarnings() + throws SQLException + { + return delegate.getWarnings(); + } + + public void clearWarnings() + throws SQLException + { + delegate.clearWarnings(); + } + + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.createStatement(resultSetType, resultSetConcurrency); + } + + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); + } + + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.prepareCall(sql, resultSetType, resultSetConcurrency); + } + + public Map> getTypeMap() + throws SQLException + { + return delegate.getTypeMap(); + } + + public void setTypeMap(Map> map) + throws SQLException + { + delegate.setTypeMap(map); + } + + public void setHoldability(int holdability) + throws SQLException + { + delegate.setHoldability(holdability); + } + + public int getHoldability() + throws SQLException + { + return delegate.getHoldability(); + } + + public Savepoint setSavepoint() + throws SQLException + { + return delegate.setSavepoint(); + } + + public Savepoint setSavepoint(String name) + throws SQLException + { + return delegate.setSavepoint(name); + } + + public void rollback(Savepoint savepoint) + throws SQLException + { + delegate.rollback(savepoint); + } + + public void releaseSavepoint(Savepoint savepoint) + throws SQLException + { + delegate.releaseSavepoint(savepoint); + } + + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.prepareStatement(sql, autoGeneratedKeys); + } + + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.prepareStatement(sql, columnIndexes); + } + + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException + { + return delegate.prepareStatement(sql, columnNames); + } + + public Clob createClob() + throws SQLException + { + return delegate.createClob(); + } + + public Blob createBlob() + throws SQLException + { + return delegate.createBlob(); + } + + public NClob createNClob() + throws SQLException + { + return delegate.createNClob(); + } + + public SQLXML createSQLXML() + throws SQLException + { + return delegate.createSQLXML(); + } + + public boolean isValid(int timeout) + throws SQLException + { + return delegate.isValid(timeout); + } + + public void setClientInfo(String name, String value) + throws SQLClientInfoException + { + delegate.setClientInfo(name, value); + } + + public void setClientInfo(Properties properties) + throws SQLClientInfoException + { + delegate.setClientInfo(properties); + } + + public String getClientInfo(String name) + throws SQLException + { + return delegate.getClientInfo(name); + } + + public Properties getClientInfo() + throws SQLException + { + return delegate.getClientInfo(); + } + + public Array createArrayOf(String typeName, Object[] elements) + throws SQLException + { + return delegate.createArrayOf(typeName, elements); + } + + public Struct createStruct(String typeName, Object[] attributes) + throws SQLException + { + return delegate.createStruct(typeName, attributes); + } + + public T unwrap(Class iface) + throws SQLException + { + return delegate.unwrap(iface); + } + + public boolean isWrapperFor(Class iface) + throws SQLException + { + return delegate.isWrapperFor(iface); + } +} diff --git a/src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java b/src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java new file mode 100644 index 000000000..dba32b4d0 --- /dev/null +++ b/src/jdk_1.6/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java @@ -0,0 +1,634 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; + +public class PreparedStatementDelegator implements PreparedStatement +{ + private final PreparedStatement delegate; + + public PreparedStatementDelegator(PreparedStatement delegate) + { + this.delegate = delegate; + } + + public ResultSet executeQuery() + throws SQLException + { + return delegate.executeQuery(); + } + + public int executeUpdate() + throws SQLException + { + return delegate.executeUpdate(); + } + + public void setNull(int parameterIndex, int sqlType) + throws SQLException + { + delegate.setNull(parameterIndex, sqlType); + } + + public void setBoolean(int parameterIndex, boolean x) + throws SQLException + { + delegate.setBoolean(parameterIndex, x); + } + + public void setByte(int parameterIndex, byte x) + throws SQLException + { + delegate.setByte(parameterIndex, x); + } + + public void setShort(int parameterIndex, short x) + throws SQLException + { + delegate.setShort(parameterIndex, x); + } + + public void setInt(int parameterIndex, int x) + throws SQLException + { + delegate.setInt(parameterIndex, x); + } + + public void setLong(int parameterIndex, long x) + throws SQLException + { + delegate.setLong(parameterIndex, x); + } + + public void setFloat(int parameterIndex, float x) + throws SQLException + { + delegate.setFloat(parameterIndex, x); + } + + public void setDouble(int parameterIndex, double x) + throws SQLException + { + delegate.setDouble(parameterIndex, x); + } + + public void setBigDecimal(int parameterIndex, BigDecimal x) + throws SQLException + { + delegate.setBigDecimal(parameterIndex, x); + } + + public void setString(int parameterIndex, String x) + throws SQLException + { + delegate.setString(parameterIndex, x); + } + + public void setBytes(int parameterIndex, byte[] x) + throws SQLException + { + delegate.setBytes(parameterIndex, x); + } + + public void setDate(int parameterIndex, Date x) + throws SQLException + { + delegate.setDate(parameterIndex, x); + } + + public void setTime(int parameterIndex, Time x) + throws SQLException + { + delegate.setTime(parameterIndex, x); + } + + public void setTimestamp(int parameterIndex, Timestamp x) + throws SQLException + { + delegate.setTimestamp(parameterIndex, x); + } + + public void setAsciiStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setAsciiStream(parameterIndex, x, length); + } + + @SuppressWarnings("deprecation") + public void setUnicodeStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setUnicodeStream(parameterIndex, x, length); + } + + public void setBinaryStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setBinaryStream(parameterIndex, x, length); + } + + public void clearParameters() + throws SQLException + { + delegate.clearParameters(); + } + + public void setObject(int parameterIndex, Object x, int targetSqlType) + throws SQLException + { + delegate.setObject(parameterIndex, x, targetSqlType); + } + + public void setObject(int parameterIndex, Object x) + throws SQLException + { + delegate.setObject(parameterIndex, x); + } + + public boolean execute() + throws SQLException + { + return delegate.execute(); + } + + public void addBatch() + throws SQLException + { + delegate.addBatch(); + } + + public void setCharacterStream(int parameterIndex, Reader reader, int length) + throws SQLException + { + delegate.setCharacterStream(parameterIndex, reader, length); + } + + public void setRef(int parameterIndex, Ref x) + throws SQLException + { + delegate.setRef(parameterIndex, x); + } + + public void setBlob(int parameterIndex, Blob x) + throws SQLException + { + delegate.setBlob(parameterIndex, x); + } + + public void setClob(int parameterIndex, Clob x) + throws SQLException + { + delegate.setClob(parameterIndex, x); + } + + public void setArray(int parameterIndex, Array x) + throws SQLException + { + delegate.setArray(parameterIndex, x); + } + + public ResultSetMetaData getMetaData() + throws SQLException + { + return delegate.getMetaData(); + } + + public void setDate(int parameterIndex, Date x, Calendar cal) + throws SQLException + { + delegate.setDate(parameterIndex, x, cal); + } + + public void setTime(int parameterIndex, Time x, Calendar cal) + throws SQLException + { + delegate.setTime(parameterIndex, x, cal); + } + + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) + throws SQLException + { + delegate.setTimestamp(parameterIndex, x, cal); + } + + public void setNull(int parameterIndex, int sqlType, String typeName) + throws SQLException + { + delegate.setNull(parameterIndex, sqlType, typeName); + } + + public void setURL(int parameterIndex, URL x) + throws SQLException + { + delegate.setURL(parameterIndex, x); + } + + public ParameterMetaData getParameterMetaData() + throws SQLException + { + return delegate.getParameterMetaData(); + } + + public void setRowId(int parameterIndex, RowId x) + throws SQLException + { + delegate.setRowId(parameterIndex, x); + } + + public void setNString(int parameterIndex, String value) + throws SQLException + { + delegate.setNString(parameterIndex, value); + } + + public void setNCharacterStream(int parameterIndex, Reader value, long length) + throws SQLException + { + delegate.setNCharacterStream(parameterIndex, value, length); + } + + public void setNClob(int parameterIndex, NClob value) + throws SQLException + { + delegate.setNClob(parameterIndex, value); + } + + public void setClob(int parameterIndex, Reader reader, long length) + throws SQLException + { + delegate.setClob(parameterIndex, reader, length); + } + + public void setBlob(int parameterIndex, InputStream inputStream, long length) + throws SQLException + { + delegate.setBlob(parameterIndex, inputStream, length); + } + + public void setNClob(int parameterIndex, Reader reader, long length) + throws SQLException + { + delegate.setNClob(parameterIndex, reader, length); + } + + public void setSQLXML(int parameterIndex, SQLXML xmlObject) + throws SQLException + { + delegate.setSQLXML(parameterIndex, xmlObject); + } + + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) + throws SQLException + { + delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + public void setAsciiStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + delegate.setAsciiStream(parameterIndex, x, length); + } + + public void setBinaryStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + delegate.setBinaryStream(parameterIndex, x, length); + } + + public void setCharacterStream(int parameterIndex, Reader reader, long length) + throws SQLException + { + delegate.setCharacterStream(parameterIndex, reader, length); + } + + public void setAsciiStream(int parameterIndex, InputStream x) + throws SQLException + { + delegate.setAsciiStream(parameterIndex, x); + } + + public void setBinaryStream(int parameterIndex, InputStream x) + throws SQLException + { + delegate.setBinaryStream(parameterIndex, x); + } + + public void setCharacterStream(int parameterIndex, Reader reader) + throws SQLException + { + delegate.setCharacterStream(parameterIndex, reader); + } + + public void setNCharacterStream(int parameterIndex, Reader value) + throws SQLException + { + delegate.setNCharacterStream(parameterIndex, value); + } + + public void setClob(int parameterIndex, Reader reader) + throws SQLException + { + delegate.setClob(parameterIndex, reader); + } + + public void setBlob(int parameterIndex, InputStream inputStream) + throws SQLException + { + delegate.setBlob(parameterIndex, inputStream); + } + + public void setNClob(int parameterIndex, Reader reader) + throws SQLException + { + delegate.setNClob(parameterIndex, reader); + } + + public ResultSet executeQuery(String sql) + throws SQLException + { + return delegate.executeQuery(sql); + } + + public int executeUpdate(String sql) + throws SQLException + { + return delegate.executeUpdate(sql); + } + + public void close() + throws SQLException + { + delegate.close(); + } + + public int getMaxFieldSize() + throws SQLException + { + return delegate.getMaxFieldSize(); + } + + public void setMaxFieldSize(int max) + throws SQLException + { + delegate.setMaxFieldSize(max); + } + + public int getMaxRows() + throws SQLException + { + return delegate.getMaxRows(); + } + + public void setMaxRows(int max) + throws SQLException + { + delegate.setMaxRows(max); + } + + public void setEscapeProcessing(boolean enable) + throws SQLException + { + delegate.setEscapeProcessing(enable); + } + + public int getQueryTimeout() + throws SQLException + { + return delegate.getQueryTimeout(); + } + + public void setQueryTimeout(int seconds) + throws SQLException + { + delegate.setQueryTimeout(seconds); + } + + public void cancel() + throws SQLException + { + delegate.cancel(); + } + + public SQLWarning getWarnings() + throws SQLException + { + return delegate.getWarnings(); + } + + public void clearWarnings() + throws SQLException + { + delegate.clearWarnings(); + } + + public void setCursorName(String name) + throws SQLException + { + delegate.setCursorName(name); + } + + public boolean execute(String sql) + throws SQLException + { + return delegate.execute(sql); + } + + public ResultSet getResultSet() + throws SQLException + { + return delegate.getResultSet(); + } + + public int getUpdateCount() + throws SQLException + { + return delegate.getUpdateCount(); + } + + public boolean getMoreResults() + throws SQLException + { + return delegate.getMoreResults(); + } + + public void setFetchDirection(int direction) + throws SQLException + { + delegate.setFetchDirection(direction); + } + + public int getFetchDirection() + throws SQLException + { + return delegate.getFetchDirection(); + } + + public void setFetchSize(int rows) + throws SQLException + { + delegate.setFetchSize(rows); + } + + public int getFetchSize() + throws SQLException + { + return delegate.getFetchSize(); + } + + public int getResultSetConcurrency() + throws SQLException + { + return delegate.getResultSetConcurrency(); + } + + public int getResultSetType() + throws SQLException + { + return delegate.getResultSetType(); + } + + public void addBatch(String sql) + throws SQLException + { + delegate.addBatch(sql); + } + + public void clearBatch() + throws SQLException + { + delegate.clearBatch(); + } + + public int[] executeBatch() + throws SQLException + { + return delegate.executeBatch(); + } + + public Connection getConnection() + throws SQLException + { + return delegate.getConnection(); + } + + public boolean getMoreResults(int current) + throws SQLException + { + return delegate.getMoreResults(current); + } + + public ResultSet getGeneratedKeys() + throws SQLException + { + return delegate.getGeneratedKeys(); + } + + public int executeUpdate(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.executeUpdate(sql, autoGeneratedKeys); + } + + public int executeUpdate(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.executeUpdate(sql, columnIndexes); + } + + public int executeUpdate(String sql, String[] columnNames) + throws SQLException + { + return delegate.executeUpdate(sql, columnNames); + } + + public boolean execute(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.execute(sql, autoGeneratedKeys); + } + + public boolean execute(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.execute(sql, columnIndexes); + } + + public boolean execute(String sql, String[] columnNames) + throws SQLException + { + return delegate.execute(sql, columnNames); + } + + public int getResultSetHoldability() + throws SQLException + { + return delegate.getResultSetHoldability(); + } + + public boolean isClosed() + throws SQLException + { + return delegate.isClosed(); + } + + public void setPoolable(boolean poolable) + throws SQLException + { + delegate.setPoolable(poolable); + } + + public boolean isPoolable() + throws SQLException + { + return delegate.isPoolable(); + } + + public T unwrap(Class iface) + throws SQLException + { + return delegate.unwrap(iface); + } + + public boolean isWrapperFor(Class iface) + throws SQLException + { + return delegate.isWrapperFor(iface); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/BeanIdList.java b/src/main/java/com/avaje/ebeaninternal/api/BeanIdList.java new file mode 100644 index 000000000..7d3df1d4e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/BeanIdList.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.util.List; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + +import javax.persistence.PersistenceException; + +/** + * Wrapper of the list of Id's adding support for background fetching + * future object. + * + * @author rbygrave + */ +public class BeanIdList { + + private final List idList; + + private boolean hasMore = true; + + private FutureTask fetchFuture; + + public BeanIdList(List idList) { + this.idList = idList; + } + + /** + * Return true if the fetch is continuing in a background thread. + */ + public boolean isFetchingInBackground() { + return fetchFuture != null; + } + + /** + * Set the FutureTask that is continuing the fetch in a background thread. + */ + public void setBackgroundFetch(FutureTask fetchFuture) { + this.fetchFuture = fetchFuture; + } + + /** + * Wait for the background fetching to complete with a timeout. + */ + public void backgroundFetchWait(long wait, TimeUnit timeUnit) { + if (fetchFuture != null){ + try { + fetchFuture.get(wait, timeUnit); + } catch (Exception e) { + throw new PersistenceException(e); + } + } + } + + /** + * Wait for the background fetching to complete. + */ + public void backgroundFetchWait() { + if (fetchFuture != null){ + try { + fetchFuture.get(); + } catch (Exception e) { + throw new PersistenceException(e); + } + } + } + + /** + * Add an Id to the list. + */ + public void add(Object id){ + idList.add(id); + } + + /** + * Return the list of Id's. + */ + public List getIdList() { + return idList; + } + + /** + * Return true if max rows was hit and there is more rows to fetch. + */ + public boolean isHasMore() { + return hasMore; + } + + /** + * Set to true when max rows is hit and there are more rows to fetch. + */ + public void setHasMore(boolean hasMore) { + this.hasMore = hasMore; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/BindParams.java b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java new file mode 100644 index 000000000..95bfc75e8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java @@ -0,0 +1,536 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam; + +/** + * Parameters used for binding to a statement. + *

+ * Used by FindByNativeSql and UpdateSql to support ordered and named + * parameters. Note that you can use either ordered OR named parameters. + *

+ */ +public class BindParams implements Serializable { + + private static final long serialVersionUID = 4541081933302086285L; + + private ArrayList positionedParameters = new ArrayList(); + + private HashMap namedParameters = new HashMap(); + + /** + * Need to create a hash when binding collection values (for in clauses). + */ + private int queryPlanHash = 1; + + /** + * This is the sql. For named parameters this is the sql after the named + * parameters have been replaced with question mark place holders and the + * parameters have been ordered by addNamedParamInOrder(). + */ + private String preparedSql; + + /** + * Return a deep copy of the BindParams. + */ + public BindParams copy() { + BindParams copy = new BindParams(); + for (Param p : positionedParameters) { + copy.positionedParameters.add(p.copy()); + } + Iterator> it = namedParameters.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + copy.namedParameters.put(entry.getKey(), entry.getValue().copy()); + } + return copy; + } + + public int queryBindHash() { + int hc = namedParameters.hashCode(); + for (int i = 0; i < positionedParameters.size(); i++) { + hc = hc * 31 + positionedParameters.get(i).hashCode(); + } + return hc; + } + + public int hashCode() { + int hc = getClass().hashCode(); + hc = hc * 31 + namedParameters.hashCode(); + for (int i = 0; i < positionedParameters.size(); i++) { + hc = hc * 31 + positionedParameters.get(i).hashCode(); + } + hc = hc * 31 + (preparedSql == null ? 0 : preparedSql.hashCode()); + return hc; + } + + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o == this) { + return true; + } + if (o instanceof BindParams) { + return hashCode() == o.hashCode(); + } + return false; + } + + /** + * Return true if there are no bind parameters. + */ + public boolean isEmpty() { + return positionedParameters.isEmpty() && namedParameters.isEmpty(); + } + + /** + * Return a Natural Key bind param if supported. + */ + public NaturalKeyBindParam getNaturalKeyBindParam() { + if (positionedParameters != null){ + return null; + } + if (namedParameters != null && namedParameters.size() == 1){ + Entry e = namedParameters.entrySet().iterator().next(); + return new NaturalKeyBindParam(e.getKey(), e.getValue().getInValue()); + } + return null; + } + + public int size() { + return positionedParameters.size(); + } + + /** + * Return true if named parameters are being used and they have not yet been + * ordered. The sql needs to be prepared (named replaced with ?) and the + * parameters ordered. + */ + public boolean requiresNamedParamsPrepare() { + return !namedParameters.isEmpty() && positionedParameters.isEmpty(); + } + + /** + * Set a null parameter using position. + */ + public void setNullParameter(int position, int jdbcType) { + Param p = getParam(position); + p.setInNullType(jdbcType); + } + + /** + * Set an In Out parameter using position. + */ + public void setParameter(int position, Object value, int outType) { + + addToQueryPlanHash(String.valueOf(position), value); + + Param p = getParam(position); + p.setInValue(value); + p.setOutType(outType); + } + + /** + * Using position set the In value of a parameter. Note that for nulls you + * must use setNullParameter. + */ + public void setParameter(int position, Object value) { + + addToQueryPlanHash(String.valueOf(position), value); + + Param p = getParam(position); + p.setInValue(value); + } + + /** + * Register the parameter as an Out parameter using position. + */ + public void registerOut(int position, int outType) { + Param p = getParam(position); + p.setOutType(outType); + } + + private Param getParam(String name) { + Param p = (Param) namedParameters.get(name); + if (p == null) { + p = new Param(); + namedParameters.put(name, p); + } + return p; + } + + private Param getParam(int position) { + int more = position - positionedParameters.size(); + if (more > 0) { + for (int i = 0; i < more; i++) { + positionedParameters.add(new Param()); + } + } + return (Param) positionedParameters.get(position - 1); + } + + /** + * Set a named In Out parameter. + */ + public void setParameter(String name, Object value, int outType) { + + addToQueryPlanHash(name, value); + + Param p = getParam(name); + p.setInValue(value); + p.setOutType(outType); + } + + /** + * Set a named In parameter that is null. + */ + public void setNullParameter(String name, int jdbcType) { + Param p = getParam(name); + p.setInNullType(jdbcType); + } + + /** + * Set a named In parameter that is not null. + */ + public Param setParameter(String name, Object value) { + + addToQueryPlanHash(name, value); + + Param p = getParam(name); + p.setInValue(value); + return p; + } + + /** + * For binding collections calculate a hash to be used for the query plan. + */ + private void addToQueryPlanHash(String name, Object value){ + if (value != null){ + if (value instanceof Collection){ + queryPlanHash = queryPlanHash * 31 + name.hashCode(); + queryPlanHash = queryPlanHash * 31 + ((Collection)value).size(); + } + } + } + + /** + * Return the hash that should be included with the query plan. + *

+ * This is to handle binding collections to in clauses. The number + * of values in the collection effects the query (number of bind values) + * and so must be taken into account when calculating the query hash. + *

+ */ + public int getQueryPlanHash() { + return queryPlanHash; + } + + /** + * Set an encryption key as a bind value. + *

+ * Needs special treatment as the value should not be included in a log. + *

+ */ + public Param setEncryptionKey(String name, Object value) { + Param p = getParam(name); + p.setEncryptionKey(value); + return p; + } + + /** + * Register the named parameter as an Out parameter. + */ + public void registerOut(String name, int outType) { + Param p = getParam(name); + p.setOutType(outType); + } + + /** + * Return the Parameter for a given position. + */ + public Param getParameter(int position) { + // Used to read Out value by CallableSql + return getParam(position); + } + + /** + * Return the named parameter. + */ + public Param getParameter(String name) { + return getParam(name); + } + + /** + * Return the values of ordered parameters. + */ + public List positionedParameters() { + return positionedParameters; + } + + /** + * Set the sql with named parameters replaced with place holder ?. + */ + public void setPreparedSql(String preparedSql) { + this.preparedSql = preparedSql; + } + + /** + * Return the sql with ? place holders (named parameters have been processed + * and ordered). + */ + public String getPreparedSql() { + return preparedSql; + } + + /** + * The bind parameters in the correct binding order. + *

+ * This is the result of converting sql with named parameters + * into sql with ? and ordered parameters. + *

+ */ + public static final class OrderedList { + + final List paramList; + + final StringBuilder preparedSql; + + public OrderedList() { + this(new ArrayList()); + } + + public OrderedList(List paramList) { + this.paramList = paramList; + this.preparedSql = new StringBuilder(); + } + + /** + * Add a parameter in the correct binding order. + */ + public void add(Param param) { + paramList.add(param); + } + + /** + * Return the number of bind parameters in this list. + */ + public int size() { + return paramList.size(); + } + + /** + * Returns the ordered list of bind parameters. + */ + public List list() { + return paramList; + } + + /** + * Append parsedSql that has named parameters converted into ?. + */ + public void appendSql(String parsedSql) { + preparedSql.append(parsedSql); + } + + public String getPreparedSql() { + return preparedSql.toString(); + } + } + + /** + * A In Out capable parameter for the CallableStatement. + */ + public static final class Param implements Serializable { + + private static final long serialVersionUID = 1L; + + private boolean encryptionKey; + + private boolean isInParam; + + private boolean isOutParam; + + private int type; + + private Object inValue; + + private Object outValue; + + private int textLocation; + + /** + * Construct a Parameter. + */ + public Param() { + } + + /** + * Create a deep copy of the Param. + */ + public Param copy() { + Param copy = new Param(); + copy.isInParam = isInParam; + copy.isOutParam = isOutParam; + copy.type = type; + copy.inValue = inValue; + copy.outValue = outValue; + return copy; + } + + public int hashCode() { + int hc = getClass().hashCode(); + hc = hc * 31 + (isInParam ? 0 : 1); + hc = hc * 31 + (isOutParam ? 0 : 1); + hc = hc * 31 + (type); + hc = hc * 31 + (inValue == null ? 0 : inValue.hashCode()); + return hc; + } + + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o == this) { + return true; + } + if (o instanceof Param) { + return hashCode() == o.hashCode(); + } + return false; + } + + /** + * Return true if this is an In parameter that needs to be bound before + * execution. + */ + public boolean isInParam() { + return isInParam; + } + + /** + * Return true if this is an out parameter that needs to be registered + * before execution. + */ + public boolean isOutParam() { + return isOutParam; + } + + /** + * Return the jdbc type of this parameter. Used for registering Out + * parameters and setting NULL In parameters. + */ + public int getType() { + return type; + } + + /** + * Set the Out parameter type. + */ + public void setOutType(int type) { + this.type = type; + this.isOutParam = true; + } + + /** + * Set the In value. + */ + public void setInValue(Object in) { + this.inValue = in; + this.isInParam = true; + } + + /** + * Set an encryption key (which can not be logged). + */ + public void setEncryptionKey(Object in) { + this.inValue = in; + this.isInParam = true; + this.encryptionKey = true; + } + + /** + * Specify that the In parameter is NULL and the specific type that it + * is. + */ + public void setInNullType(int type) { + this.type = type; + this.inValue = null; + this.isInParam = true; + } + + /** + * Return the OUT value that was retrieved. This value is set after + * CallableStatement was executed. + */ + public Object getOutValue() { + return outValue; + } + + /** + * Return the In value. If this is null, then the type should be used to + * specify the type of the null. + */ + public Object getInValue() { + return inValue; + } + + /** + * Set the OUT value returned by a CallableStatement after it has + * executed. + */ + public void setOutValue(Object out) { + this.outValue = out; + } + + /** + * Return the location this parameter was found in the sql text. + */ + public int getTextLocation() { + return textLocation; + } + + /** + * Set the location in the sql text this parameter was located. This is + * used to control order for named parameters. + */ + public void setTextLocation(int textLocation) { + this.textLocation = textLocation; + } + + /** + * If true do not include this value in a transaction log. + */ + public boolean isEncryptionKey() { + return encryptionKey; + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/ClassLoadContext.java b/src/main/java/com/avaje/ebeaninternal/api/ClassLoadContext.java new file mode 100644 index 000000000..efd469b23 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/ClassLoadContext.java @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2010 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Wraps the caller and context class loaders. + *

+ * Helper for ClassUtil. + *

+ * + * @author rbygrave + * + */ +class ClassLoadContext { + + private static final Logger logger = Logger.getLogger(ClassLoadContext.class.getName()); + + private final ClassLoader callerLoader; + + private final ClassLoader contextLoader; + + private final boolean preferContext; + + private boolean ambiguous; + + public static ClassLoadContext of(Class caller, boolean preferContext) { + return new ClassLoadContext(caller, preferContext); + } + + /** + * This constructor is package-private to restrict instantiation to + * {@link ClassLoadContextFactory} only. + */ + ClassLoadContext(final Class caller, boolean preferContext) { + if (caller == null){ + throw new IllegalArgumentException("caller is null"); + } + this.callerLoader = caller.getClassLoader(); + this.contextLoader = Thread.currentThread().getContextClassLoader(); + this.preferContext = preferContext; + } + + public Class forName(String name) throws ClassNotFoundException { + + ClassLoader defaultLoader = getDefault(preferContext); + + try { + return Class.forName(name, true, defaultLoader); + } catch (ClassNotFoundException e) { + if (callerLoader == defaultLoader) { + throw e; + } else { + return Class.forName(name, true, callerLoader); + } + } + } + + + /** + * Return the expected class loader to use. + *

+ * Works on the assumption that the child of the caller or context class + * loader is preferred. + *

+ */ + public ClassLoader getDefault(boolean preferContext) { + + if (contextLoader == null){ + if (logger.isLoggable(Level.FINE)){ + logger.fine("No Context ClassLoader, using "+callerLoader.getClass().getName()); + } + return callerLoader; + } + if (contextLoader == callerLoader){ + if (logger.isLoggable(Level.FINE)){ + logger.fine("Context and Caller ClassLoader's same instance of "+contextLoader.getClass().getName()); + } + return callerLoader; + } + + if (isChild(contextLoader, callerLoader)) { + if (logger.isLoggable(Level.FINE)){ + logger.info("Caller ClassLoader "+callerLoader.getClass().getName() + +" child of ContextLoader "+contextLoader.getClass().getName()); + } + return callerLoader; + + } else if (isChild(callerLoader, contextLoader)) { + if (logger.isLoggable(Level.FINE)){ + logger.info("Context ClassLoader "+contextLoader.getClass().getName() + +" child of Caller ClassLoader "+callerLoader.getClass().getName()); + } + return contextLoader; + + } else { + // ambiguous case, perhaps both null + logger.info("Ambiguous ClassLoader choice preferContext:"+preferContext + +" Context:"+contextLoader.getClass().getName()+" Caller:"+callerLoader.getClass().getName()); + ambiguous = true; + return preferContext ? contextLoader : callerLoader; + } + } + + /** + * Return true if the 'default' class loader is ambiguous. + */ + public boolean isAmbiguous() { + return ambiguous; + } + + /** + * Return the ClassLoader of the caller. + */ + public ClassLoader getCallerLoader() { + return callerLoader; + } + + /** + * Return the Thread Context ClassLoader. + */ + public ClassLoader getContextLoader() { + return contextLoader; + } + + /** + * Return the ClassLoader for this class. + */ + public ClassLoader getThisLoader() { + return this.getClass().getClassLoader(); + } + + /** + * Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if + * 'loader1'=='loader2']. + */ + private boolean isChild(final ClassLoader loader1, ClassLoader loader2) { +// if (loader1 == loader2) { +// logger.info("Context and Caller ClassLoader's same "+loader1.getClass().getName()); +// return true; +// } +// if (loader2 == null) { +// logger.info(msg+" ClassLoader is null"); +// return false; +// } +// if (loader1 == null) { +// logger.info("Using "+msg+" ClassLoader as other is null"); +// return true; +// } + + for (; loader2 != null; loader2 = loader2.getParent()) { + if (loader2 == loader1) { + return true; + } + } + + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java b/src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java new file mode 100644 index 000000000..53fc3bd5f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/ClassUtil.java @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2010 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.util.logging.Logger; + + +/** + * Helper to find classes taking into account the context class loader. + * + * @author rbygrave + */ +public class ClassUtil { + + private static final Logger logger = Logger.getLogger(ClassUtil.class.getName()); + + private static boolean preferContext = true; + + /** + * Load a class taking into account a context class loader (if present). + */ + public static Class forName(String name) throws ClassNotFoundException { + return forName(name, null); + } + + /** + * Load a class taking into account a context class loader (if present). + */ + public static Class forName(String name, Class caller) throws ClassNotFoundException { + + if (caller == null){ + caller = ClassUtil.class; + } + ClassLoadContext ctx = ClassLoadContext.of(caller, preferContext); + + return ctx.forName(name); + } + + + public static ClassLoader getClassLoader(Class caller, boolean preferContext) { + + if (caller == null){ + caller = ClassUtil.class; + } + ClassLoadContext ctx = ClassLoadContext.of(caller, preferContext); + ClassLoader classLoader = ctx.getDefault(preferContext); + if (ctx.isAmbiguous()){ + logger.info("Ambigous ClassLoader (Context vs Caller) chosen "+classLoader); + } + return classLoader; + } + + /** + * Return true if the given class is present. + */ + public static boolean isPresent(String className) { + return isPresent(className, null); + } + + /** + * Return true if the given class is present. + */ + public static boolean isPresent(String className, Class caller) { + try { + forName(className, caller); + return true; + } catch (Throwable ex) { + // Class or one of its dependencies is not present... + return false; + } + } + + /** + * Return a new instance of the class using the default constructor. + */ + public static Object newInstance(String className) { + return newInstance(className,null); + } + + /** + * Return a new instance of the class using the default constructor. + */ + public static Object newInstance(String className, Class caller) { + + try { + Class cls = forName(className, caller); + return cls.newInstance(); + } catch (Exception e){ + String msg = "Error constructing "+className; + throw new IllegalArgumentException(msg, e); + } + } +} + diff --git a/src/main/java/com/avaje/ebeaninternal/api/DerivedRelationshipData.java b/src/main/java/com/avaje/ebeaninternal/api/DerivedRelationshipData.java new file mode 100644 index 000000000..4e6ede8f5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/DerivedRelationshipData.java @@ -0,0 +1,27 @@ +package com.avaje.ebeaninternal.api; + +public final class DerivedRelationshipData { + + private final Object assocBean; + private final String logicalName; + private final Object bean; + + public DerivedRelationshipData(Object assocBean, String logicalName, Object bean) { + this.assocBean = assocBean; + this.logicalName = logicalName; + this.bean = bean; + } + + public Object getAssocBean() { + return assocBean; + } + + public String getLogicalName() { + return logicalName; + } + + public Object getBean() { + return bean; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/HelpScopeTrans.java b/src/main/java/com/avaje/ebeaninternal/api/HelpScopeTrans.java new file mode 100644 index 000000000..7d01952a9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/HelpScopeTrans.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.TxScope; + +/** + * Helper object to make AOP generated code simpler. + */ +public class HelpScopeTrans { + + /** + * Create a ScopeTrans for a given methods TxScope. + */ + public static ScopeTrans createScopeTrans(TxScope txScope) { + + EbeanServer server = Ebean.getServer(txScope.getServerName()); + SpiEbeanServer iserver = (SpiEbeanServer)server; + return iserver.createScopeTrans(txScope); + } + + /** + * Exiting the method in an expected fashion. + *

+ * That is returning successfully or via a caught exception. + * Unexpected exceptions are caught via the Thread uncaughtExceptionHandler. + *

+ * @param returnOrThrowable the return or throwable object + * @param opCode the opcode for ATHROW or ARETURN etc + * @param scopeTrans the scoped transaction the method was run with. + */ + public static void onExitScopeTrans(Object returnOrThrowable, int opCode, ScopeTrans scopeTrans){ + + scopeTrans.onExit(returnOrThrowable, opCode); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java new file mode 100644 index 000000000..b09af7086 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Controls the loading of ManyToOne and OneToOne relationships. + * + * @author rbygrave + */ +public interface LoadBeanContext extends LoadSecondaryQuery { + + /** + * Configure the query to load beans for this node/path. + */ + public void configureQuery(SpiQuery query, String lazyLoadProperty); + + /** + * Return the full path of this node from the root object. + */ + public String getFullPath(); + + /** + * Return the persistence context used for all queries + * related to this object graph. + */ + public PersistenceContext getPersistenceContext(); + + /** + * Return the BeanDescriptor for beans for this node. + */ + public BeanDescriptor getBeanDescriptor(); + + /** + * Return the batchSize used for lazy loading beans. + */ + public int getBatchSize(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java new file mode 100644 index 000000000..9f8d33ab1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.util.List; + +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.EntityBeanIntercept; + +/** + * Request for loading ManyToOne and OneToOne relationships. + */ +public class LoadBeanRequest extends LoadRequest { + + private final List batch; + + private final LoadBeanContext loadContext; + + private final String lazyLoadProperty; + + private final boolean loadCache; + + public LoadBeanRequest(LoadBeanContext loadContext, List batch, + Transaction transaction, int batchSize, boolean lazy, String lazyLoadProperty, boolean loadCache) { + + super(transaction, batchSize, lazy); + this.loadContext = loadContext; + this.batch = batch; + this.lazyLoadProperty = lazyLoadProperty; + this.loadCache = loadCache; + } + + public boolean isLoadCache() { + return loadCache; + } + + public String getDescription() { + String fullPath = loadContext.getFullPath(); + String s = "path:" + fullPath + " batch:" + batchSize + " actual:" + + batch.size(); + return s; + } + + /** + * Return the batch of beans to actually load. + */ + public List getBatch() { + return batch; + } + + /** + * Return the load context. + */ + public LoadBeanContext getLoadContext() { + return loadContext; + } + + /** + * Return the property that invoked the lazy loading. + */ + public String getLazyLoadProperty() { + return lazyLoadProperty; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java new file mode 100644 index 000000000..a811f59bf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; + +/** + * Controls the loading of reference objects for a query instance. + */ +public interface LoadContext { + + /** + * Return the minimum batch size when using QueryIterator with query joins. + */ + public int getSecondaryQueriesMinBatchSize(OrmQueryRequest parentRequest, int defaultQueryBatch); + + /** + * Execute any secondary (+query) queries if there are any defined. + * @param parentRequest the originating query request + */ + public void executeSecondaryQueries(OrmQueryRequest parentRequest, int defaultQueryBatch); + + /** + * Register any secondary queries (+query or +lazy) with their + * appropriate LoadBeanContext or LoadManyContext. + *

+ * This is so the LoadBeanContext or LoadManyContext use the + * defined query for +query and +lazy execution. + *

+ */ + public void registerSecondaryQueries(SpiQuery query); + + /** + * Return the node for a given path which is used by autofetch profiling. + */ + public ObjectGraphNode getObjectGraphNode(String path); + + /** + * Return the persistence context used by this query and future lazy loading. + */ + public PersistenceContext getPersistenceContext(); + + /** + * Set the persistence context used by this query and future lazy loading. + */ + public void setPersistenceContext(PersistenceContext persistenceContext); + + /** + * Register a Bean for lazy loading. + */ + public void register(String path, EntityBeanIntercept ebi); + + /** + * Register a collection for lazy loading. + */ + public void register(String path, BeanCollection bc); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java new file mode 100644 index 000000000..0fbd1b466 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; + +/** + * Controls the loading of OneToMany and ManyToMany relationships. + * + * @author rbygrave + */ +public interface LoadManyContext extends LoadSecondaryQuery { + + /** + * Configure the query to load beans for this node/path. + */ + public void configureQuery(SpiQuery query); + + /** + * Return the full path of this node from the root object. + */ + public String getFullPath(); + + /** + * Return the node location for this node/path. + */ + public ObjectGraphNode getObjectGraphNode(); + + + /** + * Return the persistence context used for all queries + * related to this object graph. + */ + public PersistenceContext getPersistenceContext(); + + /** + * Return the batchSize used for lazy loading beans. + */ + public int getBatchSize(); + + /** + * Return the BeanDescriptor for beans for this node. + */ + public BeanDescriptor getBeanDescriptor(); + + /** + * Return the associated Many bean property. + */ + public BeanPropertyAssocMany getBeanProperty(); + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java new file mode 100644 index 000000000..72f14bb07 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.util.List; + +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.BeanCollection; + +/** + * Request for loading Associated One Beans. + */ +public class LoadManyRequest extends LoadRequest { + + + private final List> batch; + + private final LoadManyContext loadContext; + + private final boolean onlyIds; + + private final boolean loadCache; + + public LoadManyRequest(LoadManyContext loadContext, + List> batch, Transaction transaction, + int batchSize, boolean lazy, boolean onlyIds, boolean loadCache) { + + super(transaction, batchSize, lazy); + this.loadContext = loadContext; + this.batch = batch; + this.onlyIds = onlyIds; + this.loadCache = loadCache; + } + + public String getDescription() { + String fullPath = loadContext.getFullPath(); + String s = "path:" + fullPath + " batch:" + batchSize + " actual:" + + batch.size(); + return s; + } + + /** + * Return the batch of collections to actually load. + */ + public List> getBatch() { + return batch; + } + + /** + * Return the load context. + */ + public LoadManyContext getLoadContext() { + return loadContext; + } + + /** + * Return true if lazy loading should only load the id values. + *

+ * This for use when lazy loading is invoked on methods such + * as clear() and removeAll() where it generally makes sense to + * only fetch the Id values as the other property information is + * not used. + *

+ */ + public boolean isOnlyIds() { + return onlyIds; + } + + /** + * Return true if we should load the Collection ids into the cache. + */ + public boolean isLoadCache() { + return loadCache; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java new file mode 100644 index 000000000..2a9148955 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.Transaction; + +/** + * Request for loading Associated One Beans. + */ +public abstract class LoadRequest { + + protected final boolean lazy; + + protected final int batchSize; + + protected final Transaction transaction; + + public LoadRequest(Transaction transaction, int batchSize, boolean lazy) { + + this.transaction = transaction; + this.batchSize = batchSize; + this.lazy = lazy; + } + + + /** + * Return true if this is a lazy load and false if it is a secondary query. + */ + public boolean isLazy() { + return lazy; + } + + /** + * Return the requested batch size. + */ + public int getBatchSize() { + return batchSize; + } + + /** + * Return the transaction to use if this is a secondary query. + *

+ * Lazy loading queries run in their own transaction. + *

+ */ + public Transaction getTransaction() { + return transaction; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java b/src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java new file mode 100644 index 000000000..33a93abc6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; + +/** + * Defines the method for executing secondary queries. + *

+ * That is +query nodes in a orm query get executed after + * the initial query as 'secondary' queries. + *

+ */ +public interface LoadSecondaryQuery { + + /** + * Execute the secondary query with a given batch size. + * + * @param parentRequest + * the originating query request + */ + public void loadSecondaryQuery(OrmQueryRequest parentRequest, int requestedBatchSize, boolean all); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java b/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java new file mode 100644 index 000000000..a9fb76ea4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.io.Serializable; +import java.util.Set; +import java.util.TreeSet; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +import com.avaje.ebeaninternal.server.query.SplitName; + +/** + * Holds the joins needs to support the many where predicates. + * These joins are independent of any 'fetch' joins on the many. + */ +public class ManyWhereJoins implements Serializable { + + private static final long serialVersionUID = -6490181101871795417L; + + private final TreeSet joins = new TreeSet(); + + /** + * Add a many where join. + */ + public void add(ElPropertyDeploy elProp) { + + String join = elProp.getElPrefix(); + BeanProperty p = elProp.getBeanProperty(); + if (p instanceof BeanPropertyAssocMany){ + join = addManyToJoin(join, p.getName()); + } + if (join != null){ + joins.add(join); + String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix(); + if (secondaryTableJoinPrefix != null) { + joins.add(join+"."+secondaryTableJoinPrefix); + } + addParentJoins(join); + } + } + + /** + * For 'many' properties we also need to add the name of the + * many property to get the full logical name of the join. + */ + private String addManyToJoin(String join, String manyPropName){ + if (join == null){ + return manyPropName; + } else { + return join+"."+manyPropName; + } + } + + private void addParentJoins(String join) { + String[] split = SplitName.split(join); + if (split[0] != null){ + joins.add(split[0]); + addParentJoins(split[0]); + } + } + + /** + * Return true if there are no extra many where joins. + */ + public boolean isEmpty() { + return joins.isEmpty(); + } + + /** + * Return the set of many where joins. + */ + public Set getJoins() { + return joins; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/Monitor.java b/src/main/java/com/avaje/ebeaninternal/api/Monitor.java new file mode 100644 index 000000000..c5f77f557 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/Monitor.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.io.Serializable; + +/** + * Object used as a synchronization monitor that is serializable. + */ +public class Monitor implements Serializable { + + private static final long serialVersionUID = -2741687226680981940L; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/ScopeTrans.java b/src/main/java/com/avaje/ebeaninternal/api/ScopeTrans.java new file mode 100644 index 000000000..33cd1f8cf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/ScopeTrans.java @@ -0,0 +1,221 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.ArrayList; + +import com.avaje.ebean.TxScope; + +/** + * Used internally to handle the scoping of transactions for methods. + */ +public class ScopeTrans implements Thread.UncaughtExceptionHandler { + + private static final int OPCODE_ATHROW = 191; + //private static final int OPCODE_ATHROW = com.avaje.ebean.enhance.asm.Opcodes.ATHROW; + + private final SpiTransactionScopeManager scopeMgr; + + /** + * The suspended transaction (can be null). + */ + private final SpiTransaction suspendedTransaction; + /** + * The transaction in scope (can be null). + */ + private final SpiTransaction transaction; + + /** + * If true by default rollback on Checked exceptions. + */ + private final boolean rollbackOnChecked; + + /** + * True if the transaction was created and hence should be committed + * on finally if it hasn't already been rolled back. + */ + private final boolean created; + + /** + * Explicit set of Exceptions that DO NOT cause a rollback to occur. + */ + private final ArrayList> noRollbackFor; + + /** + * Explicit set of Exceptions that DO cause a rollback to occur. + */ + private final ArrayList> rollbackFor; + + + private final UncaughtExceptionHandler originalUncaughtHandler; + + /** + * Flag set when a rollback has occurred. + */ + private boolean rolledBack; + + + public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope, + SpiTransaction suspendedTransaction, SpiTransactionScopeManager scopeMgr) { + + this.rollbackOnChecked = rollbackOnChecked; + this.created = created; + this.transaction = transaction; + this.suspendedTransaction = suspendedTransaction; + this.scopeMgr = scopeMgr; + + this.noRollbackFor = txScope.getNoRollbackFor(); + this.rollbackFor = txScope.getRollbackFor(); + + Thread t = Thread.currentThread(); + originalUncaughtHandler = t.getUncaughtExceptionHandler(); + + t.setUncaughtExceptionHandler(this); + } + + /** + * Called when the Thread catches any uncaught exception. + * For example, an unexpected NullPointerException or Error. + */ + public void uncaughtException(Thread thread, Throwable e) { + + // rollback transaction if required + caughtThrowable(e); + + // reinstate suspended transaction and + // original uncaughtExceptionHandler if required + onFinally(); + + if (originalUncaughtHandler != null){ + originalUncaughtHandler.uncaughtException(thread, e); + } + } + + /** + * Returned via RETURN or expected Exception from the method. + * @param returnOrThrowable the return value or Throwable + * @param opCode indicates + */ + public void onExit(Object returnOrThrowable, int opCode) { + + if (opCode == OPCODE_ATHROW){ + // exited with a Throwable + caughtThrowable((Throwable)returnOrThrowable); + } + onFinally(); + } + + + /** + * Commit if the transaction exists and has not already been rolled back. + * Also reinstate the suspended transaction if there was one. + */ + public void onFinally() { + try { + if (originalUncaughtHandler != null){ + Thread.currentThread().setUncaughtExceptionHandler(originalUncaughtHandler); + } + + if (!rolledBack && created) { + transaction.commit(); + } + + } finally { + if (suspendedTransaction != null){ + // put the previously suspended transaction + // back onto the ThreadLocal or equivalent + scopeMgr.replace(suspendedTransaction); + } + } + } + + /** + * An Error was caught and this ALWAYS causes a rollback to occur. + * Returns the error and this should be thrown by the calling code. + */ + public Error caughtError(Error e) { + rollback(e); + return e; + } + + /** + * An Exception was caught and may or may not cause a rollback to occur. + * Returns the exception and this should be thrown by the calling code. + */ + public T caughtThrowable(T e) { + + if (isRollbackThrowable(e)) { + rollback(e); + } + return e; + } + + private void rollback(Throwable e) { + if (transaction != null && transaction.isActive()) { + // transaction is null for NOT_SUPPORTED and sometimes SUPPORTS + // and Inactive (already rolled back) if nested REQUIRED + transaction.rollback(e); + } + rolledBack = true; + } + + /** + * Return true if this throwable should cause a rollback to occur. + */ + private boolean isRollbackThrowable(Throwable e) { + + if (e instanceof Error){ + return true; + } + + if (noRollbackFor != null){ + for (int i = 0; i < noRollbackFor.size(); i++) { + if (noRollbackFor.get(i).equals(e.getClass())) { + + // explicit no rollback for this one + return false; + } + } + } + + if (rollbackFor != null){ + for (int i = 0; i < rollbackFor.size(); i++) { + if (rollbackFor.get(i).equals(e.getClass())) { + // explicit rollback for this one + return true; + } + } + } + + + if (e instanceof RuntimeException) { + return true; + + } else { + // checked exceptions... + // EJB defaults this to false which is not intuitive IMO + // Ebean makes this configurable (default to true) + return rollbackOnChecked; + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiBackgroundExecutor.java b/src/main/java/com/avaje/ebeaninternal/api/SpiBackgroundExecutor.java new file mode 100644 index 000000000..6a1d7e069 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiBackgroundExecutor.java @@ -0,0 +1,14 @@ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.BackgroundExecutor; + +/** + * Internal Extension to BackgroundExecutor with shutdown. + */ +public interface SpiBackgroundExecutor extends BackgroundExecutor { + + /** + * Shutdown any associated thread pools. + */ + public void shutdown(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiCallableSql.java b/src/main/java/com/avaje/ebeaninternal/api/SpiCallableSql.java new file mode 100644 index 000000000..933c0e173 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiCallableSql.java @@ -0,0 +1,10 @@ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.CallableSql; + +public interface SpiCallableSql extends CallableSql { + + public BindParams getBindParams(); + + public TransactionEventTable getTransactionEventTable(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java new file mode 100644 index 000000000..eb06553aa --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java @@ -0,0 +1,210 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.util.List; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.TxScope; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.bean.BeanLoader; +import com.avaje.ebean.bean.CallStack; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.ddl.DdlGenerator; +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; + +/** + * Service Provider extension to EbeanServer. + */ +public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader { + + /** + * Return true if DeleteMissingChildren defaults to true for stateless updates. + */ + public boolean isDefaultDeleteMissingChildren(); + + /** + * Return true if UpdateNullProperties defaults to true for stateless updates. + */ + public boolean isDefaultUpdateNullProperties(); + + /** + * Return true if vanilla beans should be returned by queries by default. + */ + public boolean isVanillaMode(); + + /** + * Return the DatabasePlatform for this server. + */ + public DatabasePlatform getDatabasePlatform(); + + /** + * Return a JDBC driver specific handler for batching. + *

+ * Required for Oracle specific batch handling. + *

+ */ + public PstmtBatch getPstmtBatch(); + + /** + * Create an object to represent the current CallStack. + *

+ * Typically used to identify the origin of queries for Autofetch + * and object graph costing. + *

+ */ + public CallStack createCallStack(); + + /** + * Return the DDL generator. + */ + public DdlGenerator getDdlGenerator(); + + /** + * Return the AutoFetchListener. + */ + public AutoFetchManager getAutoFetchManager(); + + /** + * Clear the query execution statistics. + */ + public void clearQueryStatistics(); + + /** + * Return all the descriptors. + */ + public List> getBeanDescriptors(); + + /** + * Return the BeanDescriptor for a given type of bean. + */ + public BeanDescriptor getBeanDescriptor(Class type); + + /** + * Return BeanDescriptor using it's unique id. + */ + public BeanDescriptor getBeanDescriptorById(String descriptorId); + + /** + * Return BeanDescriptors mapped to this table. + */ + public List> getBeanDescriptors(String tableName); + + /** + * Process committed changes from another framework. + *

+ * This notifies this instance of the framework that beans have been + * committed externally to it. Either by another framework or clustered + * server. It uses this to maintain its cache and text indexes + * appropriately. + *

+ */ + public void externalModification(TransactionEventTable event); + + /** + * Create a ServerTransaction. + *

+ * To specify to use the default transaction isolation use a value of -1. + *

+ */ + public SpiTransaction createServerTransaction(boolean isExplicit, int isolationLevel); + + /** + * Return the current transaction or null if there is no current + * transaction. + */ + public SpiTransaction getCurrentServerTransaction(); + + /** + * Create a ScopeTrans for a method for the given scope definition. + */ + public ScopeTrans createScopeTrans(TxScope txScope); + + /** + * Create a ServerTransaction for query purposes. + */ + public SpiTransaction createQueryTransaction(); + + /** + * An event from another server in the cluster used to notify local + * BeanListeners of remote inserts updates and deletes. + */ + public void remoteTransactionEvent(RemoteTransactionEvent event); + + + /** + * Create a query request object. + */ + public SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery q, Transaction t); + + /** + * Compile a query. + */ + public CQuery compileQuery(Query query, Transaction t); + + /** + * Return the queryEngine for this server. + */ + public CQueryEngine getQueryEngine(); + + /** + * Execute the findId's query but without copying the query. + *

+ * Used so that the list of Id's can be made accessible to client code + * before the query has finished (if executing in a background thread). + *

+ */ + public List findIdsWithCopy(Query query, Transaction t); + + /** + * Execute the findRowCount query but without copying the query. + */ + public int findRowCountWithCopy(Query query, Transaction t); + + /** + * Load a batch of Associated One Beans. + */ + public void loadBean(LoadBeanRequest loadRequest); + + /** + * Lazy load a batch of Many's. + */ + public void loadMany(LoadManyRequest loadRequest); + + /** + * Return the default batch size for lazy loading. + */ + public int getLazyLoadBatchSize(); + + /** + * Return true if the type is known as an Entity or Xml type + * or a List Set or Map of known bean types. + */ + public boolean isSupportedType(java.lang.reflect.Type genericType); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java new file mode 100644 index 000000000..d3af546ce --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java @@ -0,0 +1,70 @@ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.Expression; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + + +/** + * An expression that becomes part of a Where clause or Having clause. + */ +public interface SpiExpression extends Expression { + + /** + * Process "Many" properties populating ManyWhereJoins. + *

+ * Predicates on Many properties require an extra independent + * join clause. + *

+ */ + public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins); + + /** + * Calculate a hash value used to identify a query for AutoFetch tuning. + *

+ * That is, if the hash changes then the query will be considered different + * from an AutoFetch perspective and get different tuning. + *

+ */ + public int queryAutoFetchHash(); + + /** + * Calculate a hash value for the expression. + * This includes the expression type and property but should exclude + * the bind values. + *

+ * This is used where queries are the same except for the bind values, in which + * case the query execution plan can be reused. + *

+ */ + public int queryPlanHash(BeanQueryRequest request); + + /** + * Return the hash value for the values that will be bound. + */ + public int queryBindHash(); + + /** + * Add some sql to the query. + *

+ * This will contain ? as a place holder for each associated bind values. + *

+ *

+ * The 'sql' added to the query can contain object property names rather + * than db tables and columns. This 'sql' is later parsed converting the + * logical property names to their full database column names. + *

+ * @param request + * the associated request. + */ + public void addSql(SpiExpressionRequest request); + + /** + * Add the parameter values to be set against query. For each ? place holder + * there should be a corresponding value that is added to the bindList. + * + * @param request + * the associated request. + */ + public void addBindValues(SpiExpressionRequest request); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java new file mode 100644 index 000000000..4f8a88ff5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebeaninternal.server.expression.FilterExprPath; + +public interface SpiExpressionFactory extends ExpressionFactory { + + /** + * Create another expression factory with a given sub path. + */ + public ExpressionFactory createExpressionFactory(FilterExprPath prefix); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java new file mode 100644 index 000000000..e1a9fb49c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java @@ -0,0 +1,71 @@ +package com.avaje.ebeaninternal.api; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Internal extension of ExpressionList. + */ +public interface SpiExpressionList extends ExpressionList { + + /** + * Return the underlying list of expressions. + */ + public List getUnderlyingList(); + + /** + * Trim the path for filterMany() expressions. + */ + public void trimPath(int prefixTrim); + + /** + * Restore the ExpressionFactory after deserialisation. + */ + public void setExpressionFactory(ExpressionFactory expr); + + /** + * Process "Many" properties populating ManyWhereJoins. + *

+ * Predicates on Many properties require an extra independent + * join clause. + *

+ */ + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoins); + + /** + * Return true if this list is empty. + */ + public boolean isEmpty(); + + /** + * Concatenate the expression sql into a String. + *

+ * The list of expressions are evaluated in order building a sql statement + * with bind parameters. + *

+ */ + public String buildSql(SpiExpressionRequest request); + + /** + * Combine the expression bind values into a list. + *

+ * Expressions are evaluated in order and all the resulting bind values are + * returned as a List. + *

+ * + * @return the list of all the bind values in order. + */ + public ArrayList buildBindValues(SpiExpressionRequest request); + + /** + * Calculate a hash based on the expressions but excluding the actual bind + * values. + */ + public int queryPlanHash(BeanQueryRequest request); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java new file mode 100644 index 000000000..e38a7e872 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java @@ -0,0 +1,52 @@ +package com.avaje.ebeaninternal.api; + +import java.util.ArrayList; + +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Request object used for gathering expression sql and bind values. + */ +public interface SpiExpressionRequest { + + /** + * Parse the logical property name to the deployment name. + */ + public String parseDeploy(String logicalProp); + + /** + * Return the bean descriptor for the root type. + */ + public BeanDescriptor getBeanDescriptor(); + + /** + * Return the associated QueryRequest. + */ + public SpiOrmQueryRequest getQueryRequest(); + + /** + * Append to the expression sql. + */ + public SpiExpressionRequest append(String sql); + + /** + * Add a bind value to this request. + */ + public void addBindValue(Object bindValue); + + /** + * Return the accumulated expression sql for all expressions in this request. + */ + public String getSql(); + + /** + * Return the ordered list of bind values for all expressions in this request. + */ + public ArrayList getBindValues(); + + /** + * Increments the parameter index and returns that value. + */ + public int nextParameter(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java new file mode 100644 index 000000000..b13bc7df1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -0,0 +1,609 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.Query; +import com.avaje.ebean.QueryListener; +import com.avaje.ebean.bean.BeanCollectionTouched; +import com.avaje.ebean.bean.CallStack; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.query.CancelableQuery; +import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +import java.util.ArrayList; +import java.util.List; + +/** + * Object Relational query - Internal extension to Query object. + */ +public interface SpiQuery extends Query { + + public enum Mode { + NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true); + Mode(boolean loadContextBean) { + this.loadContextBean = loadContextBean; + } + + private final boolean loadContextBean; + + public boolean isLoadContextBean() { + return loadContextBean; + } + } + + /** + * The type of query result. + */ + public enum Type { + + /** + * Find by Id or unique returning a single bean. + */ + BEAN, + + /** + * Find returning a List. + */ + LIST, + + /** + * Find returning a Set. + */ + SET, + + /** + * Find returning a Map. + */ + MAP, + + /** + * Find the Id's. + */ + ID_LIST, + + /** + * Find rowCount. + */ + ROWCOUNT, + + /** + * A subquery used as part of a where clause. + */ + SUBQUERY + } + + /** + * Set total hits when querying against lucene. + */ + public void setTotalHits(int totalHits); + + /** + * Return true if select all properties was used to ensure the property + * invoking a lazy load was included in the query. + */ + public boolean selectAllForLazyLoadProperty(); + + /** + * Set the query mode. + */ + public void setMode(Mode m); + + /** + * Return the query mode. + */ + public Mode getMode(); + + /** + * Return a listener that wants to be notified when the bean collection is + * first used. + */ + public BeanCollectionTouched getBeanCollectionTouched(); + + /** + * Set a listener to be notified when the bean collection has been touched + * (when the list/set/map is first used). + */ + public void setBeanCollectionTouched(BeanCollectionTouched notify); + + /** + * Set the list of Id's that is being populated. + *

+ * This is a mutating list of id's and we are setting this so that other + * threads have access to the id's before the id query has finished. + *

+ */ + public void setIdList(List ids); + + /** + * Return the list of Id's that is currently being fetched by a background + * thread. + */ + public List getIdList(); + + /** + * Return a copy of the query. + */ + public SpiQuery copy(); + + /** + * Return the type of query (List, Set, Map, Bean, rowCount etc). + */ + public Type getType(); + + /** + * Set the query type (List, Set etc). + */ + public void setType(Type type); + + /** + * Return a more detailed description of the lazy or query load. + */ + public String getLoadDescription(); + + /** + * Return the load mode (+lazy or +query). + */ + public String getLoadMode(); + + /** + * Set the load mode (+lazy or +query) and the load description. + * + * @param loadMode + * @param loadDescription + */ + public void setLoadDescription(String loadMode, String loadDescription); + + /** + * Set the BeanDescriptor for the root type of this query. + */ + public void setBeanDescriptor(BeanDescriptor desc); + + /** + * Initialise/determine the joins required to support 'many' where clause predicates. + */ + public boolean initManyWhereJoins(); + + /** + * Return the joins required to support predicates on the many properties. + */ + public ManyWhereJoins getManyWhereJoins(); + + /** + * Convert this natural key query into a find by id query. + */ + public void convertWhereNaturalKeyToId(Object idValue); + + /** + * Return a Natural Key bind parameter if supported by this query. + */ + public NaturalKeyBindParam getNaturalKeyBindParam(); + + /** + * Set the query to select the id property only. + */ + public void setSelectId(); + + /** + * Set a filter to a join path. + */ + public void setFilterMany(String prop, ExpressionList filterMany); + + /** + * Remove the query joins from query detail. + *

+ * These are registered with the Load Context. + *

+ */ + public List removeQueryJoins(); + + /** + * Remove the lazy joins from query detail. + *

+ * These are registered with the Load Context. + *

+ */ + public List removeLazyJoins(); + + /** + * Set the path of the many when +query/+lazy loading query is executed. + */ + public void setLazyLoadManyPath(String lazyLoadManyPath); + + /** + * Convert any many joins fetch joins to query joins. + */ + public void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch); + + /** + * Return the TransactionContext. + *

+ * If no TransactionContext is present on the query then the + * TransactionContext from the Transaction is used (transaction scoped + * persistence context). + *

+ */ + public PersistenceContext getPersistenceContext(); + + /** + * Set an explicit TransactionContext (typically for a refresh query). + *

+ * If no TransactionContext is present on the query then the + * TransactionContext from the Transaction is used (transaction scoped + * persistence context). + *

+ */ + public void setPersistenceContext(PersistenceContext transactionContext); + + /** + * Return true if the query detail has neither select or joins specified. + */ + public boolean isDetailEmpty(); + + /** + * Return explicit autoFetch setting or null. If null then not explicitly + * set so we use the default behaviour. + */ + public Boolean isAutofetch(); + +// /** +// * Return explicit forUpdate setting or null. +// */ +// public boolean isForUpdate(); + + /** + * If return null then no autoFetch profiling for this query. If a + * AutoFetchManager is returned this implies that profiling is turned on for + * this query (and all the objects this query creates). + */ + public AutoFetchManager getAutoFetchManager(); + + /** + * This has the effect of turning on autoFetch profiling for this query. + */ + public void setAutoFetchManager(AutoFetchManager manager); + + /** + * Return the origin point for the query. + *

+ * This MUST be call prior to a query being changed via tuning. This is + * because the queryPlanHash is used to identify the query point. + *

+ */ + public ObjectGraphNode setOrigin(CallStack callStack); + + /** + * Set the profile point of the bean or collection that is lazy loading. + *

+ * This enables use to hook this back to the original 'root' query by the + * queryPlanHash and stackPoint. + *

+ */ + public void setParentNode(ObjectGraphNode node); + + /** + * Set the property that invoked the lazy load and MUST be included in the + * lazy loading query. + */ + public void setLazyLoadProperty(String lazyLoadProperty); + + /** + * Return the property that invoked lazy load. + */ + public String getLazyLoadProperty(); + + /** + * Return the lazy load path. + */ + public String getLazyLoadManyPath(); + + /** + * Used to hook back a lazy loading query to the original query (query + * point). + *

+ * This will return null or an "original" query. + *

+ */ + public ObjectGraphNode getParentNode(); + + /** + * Return false when this is a lazy load or refresh query for a bean. + *

+ * We just take/copy the data from those beans and don't collect autoFetch + * usage profiling on those lazy load or refresh beans. + *

+ */ + public boolean isUsageProfiling(); + + /** + * Set to false if this query should not be included in the autoFetch usage + * profiling information. + */ + public void setUsageProfiling(boolean usageProfiling); + + /** + * Return the query name. + */ + public String getName(); + + /** + * Calculate a hash used by AutoFetch to identify when a query has changed + * (and hence potentially needs a new tuned query plan to be developed). + *

+ * Excludes bind values and occurs prior to AutoFetch potentially + * tuning/modifying the query. + *

+ */ + public int queryAutofetchHash(); + + /** + * Identifies queries that are the same bar the bind variables. + *

+ * This is used AFTER AutoFetch has potentially tuned the query. This is + * used to identify and reused query plans (the final SQL string and + * associated SqlTree object). + *

+ *

+ * Excludes the actual bind values (as they don't effect the query plan). + *

+ */ + public int queryPlanHash(BeanQueryRequest request); + + /** + * Calculate a hash based on the bind values used in the query. + *

+ * Combined with queryPlanHash() to return getQueryHash (a unique hash for a + * query). + *

+ */ + public int queryBindHash(); + + /** + * Identifies queries that are exactly the same including bind variables. + */ + public int queryHash(); + + /** + * Return true if this is a query based on a SqlSelect rather than + * generated. + */ + public boolean isSqlSelect(); + + /** + * Return true if this is a RawSql query. + */ + public boolean isRawSql(); + + /** + * Return the Order By clause or null if there is none defined. + */ + public OrderBy getOrderBy(); + + /** + * Return additional where clause. This should be added to any where clause + * that was part of the original query. + */ + public String getAdditionalWhere(); + + /** + * Can return null if no expressions where added to the where clause. + */ + public SpiExpressionList getWhereExpressions(); + + /** + * Can return null if no expressions where added to the having clause. + */ + public SpiExpressionList getHavingExpressions(); + + /** + * Return additional having clause. Where raw String expressions are added + * to having clause rather than Expression objects. + */ + public String getAdditionalHaving(); + + /** + * Returns true if either firstRow or maxRows has been set. + */ + public boolean hasMaxRowsOrFirstRow(); + + /** + * Return true if this query should use/check the bean cache. + */ + public Boolean isUseBeanCache(); + + /** + * Return true if this query should use/check the query cache. + */ + public boolean isUseQueryCache(); + + /** + * Return true if the beans from this query should be loaded into the bean + * cache. + */ + public boolean isLoadBeanCache(); + + /** + * Return true if the beans returned by this query should be read only. + */ + public Boolean isReadOnly(); + + /** + * Adds this bean to the persistence context prior to executing the query. + */ + public void contextAdd(EntityBean bean); + + /** + * Return the type of beans queries. + */ + public Class getBeanType(); + + /** + * Return the query timeout. + */ + public int getTimeout(); + + /** + * Return the objects that should be added to the persistence context prior + * to executing the query. + */ + public ArrayList getContextAdditions(); + + /** + * Return the bind parameters. + */ + public BindParams getBindParams(); + + /** + * Get the orm query as a String. Only available if the query was built from + * a string. + */ + public String getQuery(); + + /** + * Replace the query detail. This is used by the autoFetch feature to as a + * fast way to set the query properties and joins. + *

+ * Note care must be taken to keep the where, orderBy, firstRows and maxRows + * held in the detail attributes. + *

+ */ + public void setDetail(OrmQueryDetail detail); + + /** + * Autofetch tune the detail specifying properties to select on already defined joins + * and adding extra joins where they are missing. + */ + public boolean tuneFetchProperties(OrmQueryDetail detail); + + /** + * Set to true if this query has been tuned by autoFetch. + */ + public void setAutoFetchTuned(boolean autoFetchTuned); + + /** + * Return the query detail. + */ + public OrmQueryDetail getDetail(); + + public TableJoin getIncludeTableJoin(); + + public void setIncludeTableJoin(TableJoin includeTableJoin); + + /** + * Return the property used to specify keys for a map. + */ + public String getMapKey(); + + /** + * Return the number of rows after which fetching should occur in a + * background thread. + */ + public int getBackgroundFetchAfter(); + + /** + * Return the maximum number of rows to return in the query. + */ + public int getMaxRows(); + + /** + * Return the index of the first row to return in the query. + */ + public int getFirstRow(); + + /** + * return true if this query uses DISTINCT. + */ + public boolean isDistinct(); + + /** + * Return true if this query should build and return vanilla objects. + */ + public boolean isVanillaMode(boolean serverDefaultVanillaMode); + + /** + * Set default select clauses where none have been explicitly defined. + */ + public void setDefaultSelectClause(); + + /** + * Return the where clause from a parsed string query. + */ + public String getRawWhereClause(); + + /** + * Return the Id value. + */ + public Object getId(); + + /** + * Return the queryListener. + */ + public QueryListener getListener(); + + /** + * Return true if this query should use its own transaction. + *

+ * This is true for background fetching and when using QueryListener. + *

+ */ + public boolean createOwnTransaction(); + + /** + * Set the generated sql for debug purposes. + * + * @param generatedSql + */ + public void setGeneratedSql(String generatedSql); + + /** + * Return the hint for Statement.setFetchSize(). + */ + public int getBufferFetchSizeHint(); + + /** + * Return true if this is a query executing in the background. + */ + public boolean isFutureFetch(); + + /** + * Set to true to indicate the query is executing in a background thread + * asynchronously. + */ + public void setFutureFetch(boolean futureFetch); + + /** + * Set the underlying cancelable query (with the PreparedStatement). + */ + public void setCancelableQuery(CancelableQuery cancelableQuery); + + /** + * Return true if this query has been cancelled. + */ + public boolean isCancelled(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiSqlQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiSqlQuery.java new file mode 100644 index 000000000..2496d5b73 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiSqlQuery.java @@ -0,0 +1,77 @@ +package com.avaje.ebeaninternal.api; + +import java.sql.PreparedStatement; + +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlQueryListener; + +/** + * SQL query - Internal extension to SqlQuery. + */ +public interface SpiSqlQuery extends SqlQuery { + + /** + * Return the named or positioned parameters. + */ + public BindParams getBindParams(); + + /** + * return the query. + */ + public String getQuery(); + + /** + * Return the queryListener. + */ + public SqlQueryListener getListener(); + + /** + * Return the first row to fetch. + */ + public int getFirstRow(); + + /** + * Return the maximum number of rows to fetch. + */ + public int getMaxRows(); + + /** + * Return the number of rows after which background fetching occurs. + */ + public int getBackgroundFetchAfter(); + + /** + * Return the key property for maps. + */ + public String getMapKey(); + + /** + * Return the query timeout. + */ + public int getTimeout(); + + /** + * Return the hint for Statement.setFetchSize(). + */ + public int getBufferFetchSizeHint(); + + /** + * Return true if this is a future fetch type query. + */ + public boolean isFutureFetch(); + + /** + * Set to true if this is a future fetch type query. + */ + public void setFutureFetch(boolean futureFetch); + + /** + * Set the PreparedStatement for the purposes of supporting cancel. + */ + public void setPreparedStatement(PreparedStatement pstmt); + + /** + * Return true if the query has been cancelled. + */ + public boolean isCancelled(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java b/src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java new file mode 100644 index 000000000..d2797247e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java @@ -0,0 +1,8 @@ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.SqlUpdate; + +public interface SpiSqlUpdate extends SqlUpdate { + + public BindParams getBindParams(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java new file mode 100644 index 000000000..d6e89f668 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java @@ -0,0 +1,203 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.sql.Connection; +import java.util.List; + +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.persist.BatchControl; +import com.avaje.ebeaninternal.server.transaction.TransactionLogBuffer; + +/** + * Extends Transaction with additional API required on server. + *

+ * Provides support for batching and TransactionContext. + *

+ */ +public interface SpiTransaction extends Transaction { + + /** + * Return true if generated SQL and Bind values should be logged to the + * transaction log. + */ + public boolean isLogSql(); + + /** + * Return true if summary level events should be logged to the transaction + * log. + */ + public boolean isLogSummary(); + + /** + * Log a comment to the transaction log for Ebean INTERNAL use. There should + * always be an external LogLevel check prior to calling this method. + */ + public void logInternal(String msg); + + /** + * Return the buffer containing transaction log messages. + */ + public TransactionLogBuffer getLogBuffer(); + + /** + * Register a "Derived Relationship" (that requires an additional update). + */ + public void registerDerivedRelationship(DerivedRelationshipData assocBean); + + /** + * Return the list of "Derived Relationships" that must be maintained after + * insert. + */ + public List getDerivedRelationship(Object bean); + + /** + * Add a deleting bean to the registered list. + *

+ * This is to handle bi-directional relationships where both sides Cascade. + *

+ */ + public void registerDeleteBean(Integer hash); + + /** + * Unregister the hash of the bean. + */ + public void unregisterDeleteBean(Integer hash); + + /** + * Return true if this is a bean that has already been saved/deleted. + */ + public boolean isRegisteredDeleteBean(Integer hash); + + /** + * Unregister the persisted bean. + */ + public void unregisterBean(Object bean); + + /** + * Return true if this is a bean that has already been persisted in the + * current recursive save request. The goal is to stop recursively saving + * the bean when cascade persist is on both sides of a relationship). + *

+ * This will register the bean if it is not already. + *

+ */ + public boolean isRegisteredBean(Object bean); + + /** + * Returns a String used to identify the transaction. This id is used for + * Transaction logging. + */ + public String getId(); + + /** + * Return the batchSize specifically set for this transaction or 0. + *

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

+ */ + public int getBatchSize(); + + /** + * Modify and return the current 'depth' of the transaction. + *

+ * As we cascade save or delete we traverse the object graph tree. Going up + * to Assoc Ones the depth decreases and going down to Assoc Manys the depth + * increases. + *

+ *

+ * The depth is used for ordering batching statements. The lowest depth get + * executed first during save. + *

+ */ + public int depth(int diff); + + /** + * Return true if this transaction was created explicitly via + * Ebean.beginTransaction(). + */ + public boolean isExplicit(); + + /** + * Get the object that holds the event details. + *

+ * This information is used maintain the table state, cache and text + * indexes. On commit the Table modifications this generates is broadcast + * around the cluster (if you have a cluster). + *

+ */ + public TransactionEvent getEvent(); + + /** + * Whether persistCascade is on for save and delete. + */ + public boolean isPersistCascade(); + + /** + * Return true if this request should be batched. Conversely returns false + * if this request should be executed immediately. + */ + public boolean isBatchThisRequest(); + + /** + * Return the queue used to batch up persist requests. + */ + public BatchControl getBatchControl(); + + /** + * Set the queue used to batch up persist requests. There should only be one + * PersistQueue set per transaction. + */ + public void setBatchControl(BatchControl control); + + /** + * Return the persistence context associated with this transaction. + *

+ * You may wish to hold onto this and set it against another transaction + * later. This is along the lines of 'extended persistence context' + * behaviour. + *

+ */ + public PersistenceContext getPersistenceContext(); + + /** + * Set the persistence context to this transaction. + *

+ * This could be considered similar to 'EJB3 Extended Persistence Context'. + * In that you can get the PersistenceContext from a transaction, hold onto + * it, and then set it back later to a second transaction. In general there + * is one PersistenceContext per Transaction. The getPersistenceContext() + * and setPersistenceContext() enable a developer to reuse a single + * PersistenceContext with multiple transactions. + *

+ */ + public void setPersistenceContext(PersistenceContext context); + + /** + * Return the underlying Connection for internal use. + *

+ * If the connection is made public from Transaction and the user code calls + * that method we can no longer trust the query only status of a + * Transaction. + *

+ */ + public Connection getInternalConnection(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransactionScopeManager.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransactionScopeManager.java new file mode 100644 index 000000000..64198d67f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransactionScopeManager.java @@ -0,0 +1,6 @@ +package com.avaje.ebeaninternal.api; + +public interface SpiTransactionScopeManager { + + public void replace(SpiTransaction t); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiUpdate.java b/src/main/java/com/avaje/ebeaninternal/api/SpiUpdate.java new file mode 100644 index 000000000..57b3c3dbe --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiUpdate.java @@ -0,0 +1,76 @@ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebean.Update; + +/** + * Internal extension to the Update interface. + */ +public interface SpiUpdate extends Update { + + /** + * The type of the update request. + */ + enum OrmUpdateType { + INSERT{ + public String toString() { + return "Insert"; + } + }, + UPDATE{ + public String toString() { + return "Update"; + } + }, + DELETE{ + public String toString() { + return "Delete"; + } + }, + UNKNOWN{ + public String toString() { + return "Unknown"; + } + + }; + } + + /** + * Return the type of bean being updated. + */ + public Class getBeanType(); + + /** + * Return the type of this - insert, update or delete. + */ + public OrmUpdateType getOrmUpdateType(); + + /** + * Return the name of the table being modified. + */ + public String getBaseTable(); + + /** + * Return the update statement. This could be either sql or an orm update with bean types and property names. + */ + public String getUpdateStatement(); + + /** + * Return the timeout in seconds. + */ + public int getTimeout(); + + /** + * Return true if the cache should be notified to invalidate objects. + */ + public boolean isNotifyCache(); + + /** + * Return the bind parameters. + */ + public BindParams getBindParams(); + + /** + * Set the generated sql used. + */ + public void setGeneratedSql(String sql); +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiUpdatePlan.java b/src/main/java/com/avaje/ebeaninternal/api/SpiUpdatePlan.java new file mode 100644 index 000000000..9f24cc86c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiUpdatePlan.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.sql.SQLException; +import java.util.Set; + +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.persist.dml.DmlHandler; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; + +/** + * A plan for executing bean updates for a given set of changed properties. + *

+ * This is a cachable plan with the purpose of being being able to skip some + * phases of the update bean processing. + *

+ *

+ * The plans are cached by the BeanDescriptors. + * + * + * @author rbygrave + */ +public interface SpiUpdatePlan { + + /** + * Return true if the set clause has no columns. + *

+ * Can occur when the only columns updated have a updatable=false in their + * deployment. + *

+ */ + public boolean isEmptySetClause(); + + /** + * Bind given the request and bean. The bean could be the oldValues bean + * when binding a update or delete where clause with ALL concurrency mode. + */ + public void bindSet(DmlHandler bind, Object bean) throws SQLException; + + /** + * Return the time this plan was created. + */ + public long getTimeCreated(); + + /** + * Return the time this plan was last used. + */ + public Long getTimeLastUsed(); + + /** + * Return the hash key for this plan. + */ + public Integer getKey(); + + /** + * Return the concurrency mode for this plan. + */ + public ConcurrencyMode getMode(); + + /** + * Return the update SQL statement. + */ + public String getSql(); + + /** + * Return the set of bindable update properties. + */ + public Bindable getSet(); + + /** + * Return the properties that where changed and should be included in the + * update statement. + */ + public Set getProperties(); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java new file mode 100644 index 000000000..8fc24c37a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java @@ -0,0 +1,221 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.transaction.BeanDelta; +import com.avaje.ebeaninternal.server.transaction.DeleteByIdMap; +import com.avaje.ebeaninternal.server.transaction.IndexInvalidate; + +/** + * Holds information for a transaction. There is one TransactionEvent instance + * per Transaction instance. + *

+ * When the associated Transaction commits or rollback this information is sent + * to the TransactionEventManager. + *

+ */ +public class TransactionEvent implements Serializable { + + private static final Logger logger = Logger.getLogger(TransactionEvent.class.getName()); + + private static final long serialVersionUID = 7230903304106097120L; + + /** + * Flag indicating this is a local transaction (not from another server in + * the cluster). + */ + private transient boolean local; + + private boolean invalidateAll; + + private TransactionEventTable eventTables; + + private transient TransactionEventBeans eventBeans; + + private transient List beanDeltas; + + private transient DeleteByIdMap deleteByIdMap; + + private transient Set indexInvalidations; + + private transient Set pauseIndexInvalidate; + + /** + * Create the TransactionEvent, one per Transaction. + */ + public TransactionEvent() { + this.local = true; + } + + /** + * Set this to true to invalidate all table dependent cached objects. + */ + public void setInvalidateAll(boolean isInvalidateAll) { + this.invalidateAll = isInvalidateAll; + } + + /** + * Return true if all table states should be invalidated. This will cause + * all cached objects to be invalidated. + */ + public boolean isInvalidateAll() { + return invalidateAll; + } + + /** + * Temporarily pause/ignore any index invalidation for this bean type. + */ + public void pauseIndexInvalidate(Class beanType) { + if (pauseIndexInvalidate == null){ + pauseIndexInvalidate = new HashSet(); + } + pauseIndexInvalidate.add(beanType.getName()); + } + + /** + * Resume listening for index invalidation for this bean type. + */ + public void resumeIndexInvalidate(Class beanType) { + if (pauseIndexInvalidate != null){ + pauseIndexInvalidate.remove(beanType.getName()); + } + } + + /** + * Add an IndexInvalidation notices to the transaction. + */ + public void addIndexInvalidate(IndexInvalidate indexEvent){ + if (pauseIndexInvalidate != null && pauseIndexInvalidate.contains(indexEvent.getIndexName())){ + logger.fine("--- IGNORE Invalidate on "+indexEvent.getIndexName()); + return; + } + if (indexInvalidations == null){ + indexInvalidations = new HashSet(); + } + indexInvalidations.add(indexEvent); + } + + public void addDeleteById(BeanDescriptor desc, Object id){ + if (deleteByIdMap == null){ + deleteByIdMap = new DeleteByIdMap(); + } + deleteByIdMap.add(desc, id); + } + + public void addDeleteByIdList(BeanDescriptor desc, List idList) { + if (deleteByIdMap == null) { + deleteByIdMap = new DeleteByIdMap(); + } + deleteByIdMap.addList(desc, idList); + } + + public DeleteByIdMap getDeleteByIdMap() { + return deleteByIdMap; + } + + public void addBeanDelta(BeanDelta delta) { + if (beanDeltas == null) { + beanDeltas = new ArrayList(); + } + beanDeltas.add(delta); + } + + public List getBeanDeltas() { + return beanDeltas; + } + + /** + * Return true if this was a local transaction. Returns false if this + * transaction originated on another server in the cluster. + */ + public boolean isLocal() { + return local; + } + + /** + * For BeanListeners the requests they are interested in. + */ + public TransactionEventBeans getEventBeans() { + return eventBeans; + } + + public TransactionEventTable getEventTables() { + return eventTables; + } + + public Set getIndexInvalidations() { + return indexInvalidations; + } + + public void add(String tableName, boolean inserts, boolean updates, boolean deletes){ + if (eventTables == null){ + eventTables = new TransactionEventTable(); + } + eventTables.add(tableName, inserts, updates, deletes); + } + + public void add(TransactionEventTable table){ + if (eventTables == null){ + eventTables = new TransactionEventTable(); + } + eventTables.add(table); + } + + /** + * Add a inserted updated or deleted bean to the event. + */ + public void add(PersistRequestBean request) { + + if (request.isNotify(this)){ + // either a BeanListener or Cache is interested + if (eventBeans == null) { + eventBeans = new TransactionEventBeans(); + } + eventBeans.add(request); + } + } + + /** + * Notify the cache of bean changes. + *

+ * This returns the TransactionEventTable so that if any + * general table changes can also be used to invalidate + * parts of the cache. + *

+ */ + public void notifyCache(){ + if (eventBeans != null){ + eventBeans.notifyCache(); + } + if (deleteByIdMap != null) { + deleteByIdMap.notifyCache(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java new file mode 100644 index 000000000..2c77545cb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.api; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; + +/** + * Lists of inserted updated and deleted beans that have a BeanPersistListener. + *

+ * These beans will be sent to the appropriate BeanListeners after a successful + * commit of the transaction. + *

+ */ +public class TransactionEventBeans { + + ArrayList> requests = new ArrayList>(); + + /** + * Return the list of PersistRequests that BeanListeners are interested in. + */ + public List> getRequests() { + return requests; + } + + /** + * Add a bean for BeanListener notification. + */ + public void add(PersistRequestBean request) { + + requests.add(request); + } + + public void notifyCache() { + for (int i = 0; i < requests.size(); i++) { + requests.get(i).notifyCache(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEventTable.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEventTable.java new file mode 100644 index 000000000..7b91bc88d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEventTable.java @@ -0,0 +1,143 @@ +package com.avaje.ebeaninternal.api; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.avaje.ebean.event.BulkTableEvent; +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public final class TransactionEventTable implements Serializable { + + private static final long serialVersionUID = 2236555729767483264L; + + private final Map map = new HashMap(); + + public String toString() { + return "TransactionEventTable " + map.values(); + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + for (TableIUD tableIud : map.values()) { + tableIud.writeBinaryMessage(msgList); + } + } + + public void readBinaryMessage(DataInput dataInput) throws IOException { + + TableIUD tableIud = TableIUD.readBinaryMessage(dataInput); + map.put(tableIud.getTableName(), tableIud); + } + + + public void add(TransactionEventTable table){ + + for (TableIUD iud : table.values()) { + add(iud); + } + } + + public void add(String table, boolean insert, boolean update, boolean delete){ + + table = table.toUpperCase(); + + add(new TableIUD(table, insert, update, delete)); + } + + public void add(TableIUD newTableIUD){ + + TableIUD existingTableIUD = map.put(newTableIUD.getTableName(), newTableIUD); + if (existingTableIUD != null){ + newTableIUD.add(existingTableIUD); + } + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Collection values() { + return map.values(); + } + + public static class TableIUD implements Serializable, BulkTableEvent { + + private static final long serialVersionUID = -1958317571064162089L; + + private String table; + private boolean insert; + private boolean update; + private boolean delete; + + private TableIUD(String table, boolean insert, boolean update, boolean delete){ + this.table = table; + this.insert = insert; + this.update = update; + this.delete = delete; + } + + public static TableIUD readBinaryMessage(DataInput dataInput) throws IOException { + + String table = dataInput.readUTF(); + boolean insert = dataInput.readBoolean(); + boolean update = dataInput.readBoolean(); + boolean delete = dataInput.readBoolean(); + + return new TableIUD(table, insert, update, delete); + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + BinaryMessage msg = new BinaryMessage(table.length()+10); + DataOutputStream os = msg.getOs(); + os.writeInt(BinaryMessage.TYPE_TABLEIUD); + os.writeUTF(table); + os.writeBoolean(insert); + os.writeBoolean(update); + os.writeBoolean(delete); + + msgList.add(msg); + } + + public String toString() { + return "TableIUD "+table+" i:"+insert+" u:"+update+" d:"+delete; + } + + private void add(TableIUD other) { + if (other.insert){ + insert = true; + } + if (other.update){ + update = true; + } + if (other.delete){ + delete = true; + } + } + public String getTableName() { + return table; + } + + public boolean isInsert() { + return insert; + } + + public boolean isUpdate() { + return update; + } + + public boolean isDelete() { + return delete; + } + + public boolean isUpdateOrDelete() { + return update || delete; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/api/package-info.java b/src/main/java/com/avaje/ebeaninternal/api/package-info.java new file mode 100644 index 000000000..7ecafc143 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/package-info.java @@ -0,0 +1,4 @@ +/** + * Internal service API. + */ +package com.avaje.ebeaninternal.api; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java b/src/main/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java new file mode 100644 index 000000000..51c6e47e0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/jdbc/ConnectionDelegator.java @@ -0,0 +1,340 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.jdbc; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; + +public class ConnectionDelegator implements Connection +{ + private final Connection delegate; + + public ConnectionDelegator(Connection delegate) + { + this.delegate = delegate; + } + + public Statement createStatement() + throws SQLException + { + return delegate.createStatement(); + } + + public PreparedStatement prepareStatement(String sql) + throws SQLException + { + return delegate.prepareStatement(sql); + } + + public CallableStatement prepareCall(String sql) + throws SQLException + { + return delegate.prepareCall(sql); + } + + public String nativeSQL(String sql) + throws SQLException + { + return delegate.nativeSQL(sql); + } + + public void setAutoCommit(boolean autoCommit) + throws SQLException + { + delegate.setAutoCommit(autoCommit); + } + + public boolean getAutoCommit() + throws SQLException + { + return delegate.getAutoCommit(); + } + + public void commit() + throws SQLException + { + delegate.commit(); + } + + public void rollback() + throws SQLException + { + delegate.rollback(); + } + + public void close() + throws SQLException + { + delegate.close(); + } + + public boolean isClosed() + throws SQLException + { + return delegate.isClosed(); + } + + public DatabaseMetaData getMetaData() + throws SQLException + { + return delegate.getMetaData(); + } + + public void setReadOnly(boolean readOnly) + throws SQLException + { + delegate.setReadOnly(readOnly); + } + + public boolean isReadOnly() + throws SQLException + { + return delegate.isReadOnly(); + } + + public void setCatalog(String catalog) + throws SQLException + { + delegate.setCatalog(catalog); + } + + public String getCatalog() + throws SQLException + { + return delegate.getCatalog(); + } + + public void setTransactionIsolation(int level) + throws SQLException + { + delegate.setTransactionIsolation(level); + } + + public int getTransactionIsolation() + throws SQLException + { + return delegate.getTransactionIsolation(); + } + + public SQLWarning getWarnings() + throws SQLException + { + return delegate.getWarnings(); + } + + public void clearWarnings() + throws SQLException + { + delegate.clearWarnings(); + } + + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.createStatement(resultSetType, resultSetConcurrency); + } + + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); + } + + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + return delegate.prepareCall(sql, resultSetType, resultSetConcurrency); + } + + public Map> getTypeMap() + throws SQLException + { + return delegate.getTypeMap(); + } + + public void setTypeMap(Map> map) + throws SQLException + { + delegate.setTypeMap(map); + } + + public void setHoldability(int holdability) + throws SQLException + { + delegate.setHoldability(holdability); + } + + public int getHoldability() + throws SQLException + { + return delegate.getHoldability(); + } + + public Savepoint setSavepoint() + throws SQLException + { + return delegate.setSavepoint(); + } + + public Savepoint setSavepoint(String name) + throws SQLException + { + return delegate.setSavepoint(name); + } + + public void rollback(Savepoint savepoint) + throws SQLException + { + delegate.rollback(savepoint); + } + + public void releaseSavepoint(Savepoint savepoint) + throws SQLException + { + delegate.releaseSavepoint(savepoint); + } + + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + return delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.prepareStatement(sql, autoGeneratedKeys); + } + + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.prepareStatement(sql, columnIndexes); + } + + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException + { + return delegate.prepareStatement(sql, columnNames); + } + + public Clob createClob() + throws SQLException + { + return delegate.createClob(); + } + + public Blob createBlob() + throws SQLException + { + return delegate.createBlob(); + } + + public NClob createNClob() + throws SQLException + { + return delegate.createNClob(); + } + + public SQLXML createSQLXML() + throws SQLException + { + return delegate.createSQLXML(); + } + + public boolean isValid(int timeout) + throws SQLException + { + return delegate.isValid(timeout); + } + + public void setClientInfo(String name, String value) + throws SQLClientInfoException + { + delegate.setClientInfo(name, value); + } + + public void setClientInfo(Properties properties) + throws SQLClientInfoException + { + delegate.setClientInfo(properties); + } + + public String getClientInfo(String name) + throws SQLException + { + return delegate.getClientInfo(name); + } + + public Properties getClientInfo() + throws SQLException + { + return delegate.getClientInfo(); + } + + public Array createArrayOf(String typeName, Object[] elements) + throws SQLException + { + return delegate.createArrayOf(typeName, elements); + } + + public Struct createStruct(String typeName, Object[] attributes) + throws SQLException + { + return delegate.createStruct(typeName, attributes); + } + + public T unwrap(Class iface) + throws SQLException + { + return delegate.unwrap(iface); + } + + public boolean isWrapperFor(Class iface) + throws SQLException + { + return delegate.isWrapperFor(iface); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java b/src/main/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java new file mode 100644 index 000000000..dba32b4d0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/jdbc/PreparedStatementDelegator.java @@ -0,0 +1,634 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; + +public class PreparedStatementDelegator implements PreparedStatement +{ + private final PreparedStatement delegate; + + public PreparedStatementDelegator(PreparedStatement delegate) + { + this.delegate = delegate; + } + + public ResultSet executeQuery() + throws SQLException + { + return delegate.executeQuery(); + } + + public int executeUpdate() + throws SQLException + { + return delegate.executeUpdate(); + } + + public void setNull(int parameterIndex, int sqlType) + throws SQLException + { + delegate.setNull(parameterIndex, sqlType); + } + + public void setBoolean(int parameterIndex, boolean x) + throws SQLException + { + delegate.setBoolean(parameterIndex, x); + } + + public void setByte(int parameterIndex, byte x) + throws SQLException + { + delegate.setByte(parameterIndex, x); + } + + public void setShort(int parameterIndex, short x) + throws SQLException + { + delegate.setShort(parameterIndex, x); + } + + public void setInt(int parameterIndex, int x) + throws SQLException + { + delegate.setInt(parameterIndex, x); + } + + public void setLong(int parameterIndex, long x) + throws SQLException + { + delegate.setLong(parameterIndex, x); + } + + public void setFloat(int parameterIndex, float x) + throws SQLException + { + delegate.setFloat(parameterIndex, x); + } + + public void setDouble(int parameterIndex, double x) + throws SQLException + { + delegate.setDouble(parameterIndex, x); + } + + public void setBigDecimal(int parameterIndex, BigDecimal x) + throws SQLException + { + delegate.setBigDecimal(parameterIndex, x); + } + + public void setString(int parameterIndex, String x) + throws SQLException + { + delegate.setString(parameterIndex, x); + } + + public void setBytes(int parameterIndex, byte[] x) + throws SQLException + { + delegate.setBytes(parameterIndex, x); + } + + public void setDate(int parameterIndex, Date x) + throws SQLException + { + delegate.setDate(parameterIndex, x); + } + + public void setTime(int parameterIndex, Time x) + throws SQLException + { + delegate.setTime(parameterIndex, x); + } + + public void setTimestamp(int parameterIndex, Timestamp x) + throws SQLException + { + delegate.setTimestamp(parameterIndex, x); + } + + public void setAsciiStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setAsciiStream(parameterIndex, x, length); + } + + @SuppressWarnings("deprecation") + public void setUnicodeStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setUnicodeStream(parameterIndex, x, length); + } + + public void setBinaryStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + delegate.setBinaryStream(parameterIndex, x, length); + } + + public void clearParameters() + throws SQLException + { + delegate.clearParameters(); + } + + public void setObject(int parameterIndex, Object x, int targetSqlType) + throws SQLException + { + delegate.setObject(parameterIndex, x, targetSqlType); + } + + public void setObject(int parameterIndex, Object x) + throws SQLException + { + delegate.setObject(parameterIndex, x); + } + + public boolean execute() + throws SQLException + { + return delegate.execute(); + } + + public void addBatch() + throws SQLException + { + delegate.addBatch(); + } + + public void setCharacterStream(int parameterIndex, Reader reader, int length) + throws SQLException + { + delegate.setCharacterStream(parameterIndex, reader, length); + } + + public void setRef(int parameterIndex, Ref x) + throws SQLException + { + delegate.setRef(parameterIndex, x); + } + + public void setBlob(int parameterIndex, Blob x) + throws SQLException + { + delegate.setBlob(parameterIndex, x); + } + + public void setClob(int parameterIndex, Clob x) + throws SQLException + { + delegate.setClob(parameterIndex, x); + } + + public void setArray(int parameterIndex, Array x) + throws SQLException + { + delegate.setArray(parameterIndex, x); + } + + public ResultSetMetaData getMetaData() + throws SQLException + { + return delegate.getMetaData(); + } + + public void setDate(int parameterIndex, Date x, Calendar cal) + throws SQLException + { + delegate.setDate(parameterIndex, x, cal); + } + + public void setTime(int parameterIndex, Time x, Calendar cal) + throws SQLException + { + delegate.setTime(parameterIndex, x, cal); + } + + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) + throws SQLException + { + delegate.setTimestamp(parameterIndex, x, cal); + } + + public void setNull(int parameterIndex, int sqlType, String typeName) + throws SQLException + { + delegate.setNull(parameterIndex, sqlType, typeName); + } + + public void setURL(int parameterIndex, URL x) + throws SQLException + { + delegate.setURL(parameterIndex, x); + } + + public ParameterMetaData getParameterMetaData() + throws SQLException + { + return delegate.getParameterMetaData(); + } + + public void setRowId(int parameterIndex, RowId x) + throws SQLException + { + delegate.setRowId(parameterIndex, x); + } + + public void setNString(int parameterIndex, String value) + throws SQLException + { + delegate.setNString(parameterIndex, value); + } + + public void setNCharacterStream(int parameterIndex, Reader value, long length) + throws SQLException + { + delegate.setNCharacterStream(parameterIndex, value, length); + } + + public void setNClob(int parameterIndex, NClob value) + throws SQLException + { + delegate.setNClob(parameterIndex, value); + } + + public void setClob(int parameterIndex, Reader reader, long length) + throws SQLException + { + delegate.setClob(parameterIndex, reader, length); + } + + public void setBlob(int parameterIndex, InputStream inputStream, long length) + throws SQLException + { + delegate.setBlob(parameterIndex, inputStream, length); + } + + public void setNClob(int parameterIndex, Reader reader, long length) + throws SQLException + { + delegate.setNClob(parameterIndex, reader, length); + } + + public void setSQLXML(int parameterIndex, SQLXML xmlObject) + throws SQLException + { + delegate.setSQLXML(parameterIndex, xmlObject); + } + + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) + throws SQLException + { + delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + public void setAsciiStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + delegate.setAsciiStream(parameterIndex, x, length); + } + + public void setBinaryStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + delegate.setBinaryStream(parameterIndex, x, length); + } + + public void setCharacterStream(int parameterIndex, Reader reader, long length) + throws SQLException + { + delegate.setCharacterStream(parameterIndex, reader, length); + } + + public void setAsciiStream(int parameterIndex, InputStream x) + throws SQLException + { + delegate.setAsciiStream(parameterIndex, x); + } + + public void setBinaryStream(int parameterIndex, InputStream x) + throws SQLException + { + delegate.setBinaryStream(parameterIndex, x); + } + + public void setCharacterStream(int parameterIndex, Reader reader) + throws SQLException + { + delegate.setCharacterStream(parameterIndex, reader); + } + + public void setNCharacterStream(int parameterIndex, Reader value) + throws SQLException + { + delegate.setNCharacterStream(parameterIndex, value); + } + + public void setClob(int parameterIndex, Reader reader) + throws SQLException + { + delegate.setClob(parameterIndex, reader); + } + + public void setBlob(int parameterIndex, InputStream inputStream) + throws SQLException + { + delegate.setBlob(parameterIndex, inputStream); + } + + public void setNClob(int parameterIndex, Reader reader) + throws SQLException + { + delegate.setNClob(parameterIndex, reader); + } + + public ResultSet executeQuery(String sql) + throws SQLException + { + return delegate.executeQuery(sql); + } + + public int executeUpdate(String sql) + throws SQLException + { + return delegate.executeUpdate(sql); + } + + public void close() + throws SQLException + { + delegate.close(); + } + + public int getMaxFieldSize() + throws SQLException + { + return delegate.getMaxFieldSize(); + } + + public void setMaxFieldSize(int max) + throws SQLException + { + delegate.setMaxFieldSize(max); + } + + public int getMaxRows() + throws SQLException + { + return delegate.getMaxRows(); + } + + public void setMaxRows(int max) + throws SQLException + { + delegate.setMaxRows(max); + } + + public void setEscapeProcessing(boolean enable) + throws SQLException + { + delegate.setEscapeProcessing(enable); + } + + public int getQueryTimeout() + throws SQLException + { + return delegate.getQueryTimeout(); + } + + public void setQueryTimeout(int seconds) + throws SQLException + { + delegate.setQueryTimeout(seconds); + } + + public void cancel() + throws SQLException + { + delegate.cancel(); + } + + public SQLWarning getWarnings() + throws SQLException + { + return delegate.getWarnings(); + } + + public void clearWarnings() + throws SQLException + { + delegate.clearWarnings(); + } + + public void setCursorName(String name) + throws SQLException + { + delegate.setCursorName(name); + } + + public boolean execute(String sql) + throws SQLException + { + return delegate.execute(sql); + } + + public ResultSet getResultSet() + throws SQLException + { + return delegate.getResultSet(); + } + + public int getUpdateCount() + throws SQLException + { + return delegate.getUpdateCount(); + } + + public boolean getMoreResults() + throws SQLException + { + return delegate.getMoreResults(); + } + + public void setFetchDirection(int direction) + throws SQLException + { + delegate.setFetchDirection(direction); + } + + public int getFetchDirection() + throws SQLException + { + return delegate.getFetchDirection(); + } + + public void setFetchSize(int rows) + throws SQLException + { + delegate.setFetchSize(rows); + } + + public int getFetchSize() + throws SQLException + { + return delegate.getFetchSize(); + } + + public int getResultSetConcurrency() + throws SQLException + { + return delegate.getResultSetConcurrency(); + } + + public int getResultSetType() + throws SQLException + { + return delegate.getResultSetType(); + } + + public void addBatch(String sql) + throws SQLException + { + delegate.addBatch(sql); + } + + public void clearBatch() + throws SQLException + { + delegate.clearBatch(); + } + + public int[] executeBatch() + throws SQLException + { + return delegate.executeBatch(); + } + + public Connection getConnection() + throws SQLException + { + return delegate.getConnection(); + } + + public boolean getMoreResults(int current) + throws SQLException + { + return delegate.getMoreResults(current); + } + + public ResultSet getGeneratedKeys() + throws SQLException + { + return delegate.getGeneratedKeys(); + } + + public int executeUpdate(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.executeUpdate(sql, autoGeneratedKeys); + } + + public int executeUpdate(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.executeUpdate(sql, columnIndexes); + } + + public int executeUpdate(String sql, String[] columnNames) + throws SQLException + { + return delegate.executeUpdate(sql, columnNames); + } + + public boolean execute(String sql, int autoGeneratedKeys) + throws SQLException + { + return delegate.execute(sql, autoGeneratedKeys); + } + + public boolean execute(String sql, int[] columnIndexes) + throws SQLException + { + return delegate.execute(sql, columnIndexes); + } + + public boolean execute(String sql, String[] columnNames) + throws SQLException + { + return delegate.execute(sql, columnNames); + } + + public int getResultSetHoldability() + throws SQLException + { + return delegate.getResultSetHoldability(); + } + + public boolean isClosed() + throws SQLException + { + return delegate.isClosed(); + } + + public void setPoolable(boolean poolable) + throws SQLException + { + delegate.setPoolable(poolable); + } + + public boolean isPoolable() + throws SQLException + { + return delegate.isPoolable(); + } + + public T unwrap(Class iface) + throws SQLException + { + return delegate.unwrap(iface); + } + + public boolean isWrapperFor(Class iface) + throws SQLException + { + return delegate.isWrapperFor(iface); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java new file mode 100644 index 000000000..79929fb62 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java @@ -0,0 +1,263 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.autofetch; + +import java.util.Iterator; + +import com.avaje.ebean.Query; +import com.avaje.ebean.bean.NodeUsageListener; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.config.AutofetchMode; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Collects and manages the the profile information. + *

+ * The profile information is periodically converted into "tuned query details" - + * which is used to automatically tune the queries that use autoFetch. + *

+ *

+ * The "tuned query details" effectively are part of the query that has the + * select() and join() information (but not the where clause, order by, limits + * etc). These are applied to the query when tuneQuery() is called. + *

+ */ +public interface AutoFetchManager extends NodeUsageListener { + + /** + * Set the owning ebean server. + */ + public void setOwner(SpiEbeanServer server, ServerConfig serverConfig); + + /** + * Clear the query execution statistics. + */ + public void clearQueryStatistics(); + + /** + * Clear all the tuned query info. + *

+ * Should only need do this for testing and playing around. + *

+ */ + public int clearTunedQueryInfo(); + + /** + * Clear all the profiling information. + *

+ * This means the profiling information will need to be re-gathered. + *

+ *

+ * Should only need do this for testing and playing around. + *

+ */ + public int clearProfilingInfo(); + + /** + * On shutdown fire garbage collection and collect statistics. Note that + * usually we add a little delay (100 milliseconds) to give the garbage + * collector plenty of time to do its thing and collect the profile + * information. + */ + public void shutdown(); + + /** + * Return the current tuned fetch information for a given queryPoint key. + */ + public TunedQueryInfo getTunedQueryInfo(String queryPointKey); + + /** + * Return the current Statistics for a given queryPoint key. + */ + public Statistics getStatistics(String queryPointKey); + + /** + * Iterate the tuned fetch info. + *

+ * This should be a read only iteration. + *

+ */ + public Iterator iterateTunedQueryInfo(); + + /** + * Iterate the node usage statistics. + *

+ * This should be a read only iteration. + *

+ */ + public Iterator iterateStatistics(); + + /** + * Return true if profiling is enabled. + */ + public boolean isProfiling(); + + /** + * Set to true to enable profiling. + *

+ * We rely on garbage collection to collect the profiling information. This + * means there is a unknown delay between when a query is executed and when + * we actually collect the usage profile information. + *

+ *

+ * Due to this garbage collection delay, when turning off profiling while + * the application is running you should consider calling + * collectUsageViaGC() BEFORE setProfiling(false). This hints to + * the JVM to perform garbage collection, and hopefully collects the + * profiling information. + *

+ */ + public void setProfiling(boolean enable); + + /** + * Return true if automatic query tuning is enabled. + */ + public boolean isQueryTuning(); + + /** + * Set to true to enable automatic query tuning. + */ + public void setQueryTuning(boolean enable); + + /** + * This controls whether autoFetch is used when it has not been explicitly + * set on a query via {@link Query#setAutoFetch(boolean)}. + */ + public AutofetchMode getMode(); + + /** + * Set the auto fetch mode used when a query has not had + * {@link Query#setAutoFetch(boolean)}. + */ + public void setMode(AutofetchMode Mode); + + /** + * Return the profiling rate (int between 0 and 100). + */ + public double getProfilingRate(); + + /** + * Set the profiling rate (int between 0 and 100). + */ + public void setProfilingRate(double rate); + + /** + * Return the max number of queries profiled (per query point). + *

+ * The number of queries profiled is collected per query point. Once a query + * point has profiled this number of queries it does not profile any more. + *

+ */ + public int getProfilingBase(); + + /** + * Set a max number of queries to profile per query point. + *

+ * This number should provide a level of confidence that no more profiling + * is required for this query point. + *

+ */ + public void setProfilingBase(int profilingMax); + + /** + * Return the minimum number of queries profiled before autoFetch will start + * automatically tuning the queries. + *

+ * This could be one which means start autoFetch tuning after the first + * profiling information is collected. + *

+ */ + public int getProfilingMin(); + + /** + * Set the minimum number of queries profiled per query point before + * autoFetch will automatically tune the queries. + *

+ * Increasing this number will mean more profiling is collected before + * autoFetch starts tuning the query. + *

+ */ + public void setProfilingMin(int autoFetchMinThreshold); + + /** + * Fire a garbage collection (hint to the JVM). Assuming garbage collection + * fires this will gather the usage profiling information. + */ + public String collectUsageViaGC(long waitMillis); + + /** + * This will take the current profiling information and update the "tuned + * query detail". + *

+ * This is done periodically and can also be manually invoked. + *

+ *

+ * This returns a string summary of the updates that occurred. + *

+ */ + public String updateTunedQueryInfo(); + + /** + * Called when a query thinks it should be automatically tuned by autoFetch. + *

+ * This internally checks that autoFetch is enabled, there is a "tuned query + * detail" to tune the query with and that the autoFetchMinThreshold has + * been reached. + *

+ *

+ * This will also determine if the query should be profiled. + *

+ */ + public boolean tuneQuery(SpiQuery query); + + /** + * Collect query profiling information. + *

+ * This is for the original query as well as any subsequent lazy loading + * queries that are required as the object graph is traversed. + *

+ * + * @param node + * the node path in the object graph. + * @param beans + * the number of beans loaded by the query. + * @param micros + * the query executing time in microseconds + */ + public void collectQueryInfo(ObjectGraphNode node, int beans, int micros); + + + /** + * Return the number of queries tuned by AutoFetch. + */ + public int getTotalTunedQueryCount(); + + /** + * Return the size of the TuneQuery map. + */ + public int getTotalTunedQuerySize(); + + /** + * Return the size of the profile map. + */ + public int getTotalProfileSize(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java new file mode 100644 index 000000000..c9b7ce31f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java @@ -0,0 +1,97 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import java.io.File; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.resource.ResourceManager; + +public class AutoFetchManagerFactory { + + private static final Logger logger = Logger.getLogger(AutoFetchManagerFactory.class.getName()); + + + public static AutoFetchManager create(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) { + + AutoFetchManagerFactory me = new AutoFetchManagerFactory(); + return me.createAutoFetchManager(server, serverConfig, resourceManager); + } + + private AutoFetchManager createAutoFetchManager(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager){ + + AutoFetchManager manager = createAutoFetchManager(server.getName(), resourceManager); + manager.setOwner(server, serverConfig); + + return manager; + } + + private AutoFetchManager createAutoFetchManager(String serverName, ResourceManager resourceManager) { + + File autoFetchFile = getAutoFetchFile(serverName, resourceManager); + + AutoFetchManager autoFetchManager = null; + + boolean readFile = GlobalProperties.getBoolean("autofetch.readfromfile", true); + if (readFile) { + autoFetchManager = deserializeAutoFetch(autoFetchFile); + } + + if (autoFetchManager == null) { + // not deserialized from file so create as empty + // It will be populated automatically by querying the + // database meta data + autoFetchManager = new DefaultAutoFetchManager(autoFetchFile.getAbsolutePath()); + } + + return autoFetchManager; + } + + private AutoFetchManager deserializeAutoFetch(File autoFetchFile) { + try { + + if (!autoFetchFile.exists()) { + return null; + } + FileInputStream fi = new FileInputStream(autoFetchFile); + ObjectInputStream ois = new ObjectInputStream(fi); + AutoFetchManager profListener = (AutoFetchManager) ois.readObject(); + + logger.info("AutoFetch deserialized from file ["+autoFetchFile.getAbsolutePath()+"]"); + + return profListener; + + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error loading autofetch file "+autoFetchFile.getAbsolutePath(), ex); + return null; + } + } + + /** + * Return the file name of the autoFetch meta data. + */ + private File getAutoFetchFile(String serverName, ResourceManager resourceManager) { + + String fileName = ".ebean."+serverName+".autofetch"; + + File dir = resourceManager.getAutofetchDirectory(); + + if (!dir.exists()) { + // automatically create the directory if it does not exist. + // this is probably a fairly reasonable thing to do + if (!dir.mkdirs()) { + String m = "Unable to create directory [" + dir + "] for autofetch file ["+ fileName + "]"; + throw new PersistenceException(m); + } + } + + return new File(dir, fileName); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java new file mode 100644 index 000000000..698f05370 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java @@ -0,0 +1,582 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.CallStack; +import com.avaje.ebean.bean.NodeUsageCollector; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebean.config.AutofetchConfig; +import com.avaje.ebean.config.AutofetchMode; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; + +/** + * The manager of all the usage/query statistics as well as the tuned fetch + * information. + */ +public class DefaultAutoFetchManager implements AutoFetchManager, Serializable { + + private static final long serialVersionUID = -6826119882781771722L; + + private final String statisticsMonitor = new String(); + + private final String fileName; + + /** + * Map of the usage and query statistics gathered. + */ + private Map statisticsMap = new ConcurrentHashMap(); + + /** + * Map of the tuned query details per profile query point. + */ + private Map tunedQueryInfoMap = new ConcurrentHashMap(); + + private transient long defaultGarbageCollectionWait = 100; + + /** + * Left without synchronized for now. + */ + private transient int tunedQueryCount; + + /** + * Converted from a 0-100 int to a double. Effectively a percentage rate at + * which to collect profiling information. + */ + private transient double profilingRate = 0.1d; + + private transient int profilingBase = 10; + + private transient int profilingMin = 1; + + private transient boolean profiling; + + private transient boolean queryTuning; + + private transient boolean queryTuningAddVersion; + + private transient AutofetchMode mode; + + private transient boolean useFileLogging; + + /** + * Server that owns this Profile Listener. + */ + private transient SpiEbeanServer server; + + /** + * The logger. + */ + private transient DefaultAutoFetchManagerLogging logging; + + public DefaultAutoFetchManager(String fileName) { + this.fileName = fileName; + } + + /** + * Set up this profile listener before it is active. + */ + public void setOwner(SpiEbeanServer server, ServerConfig serverConfig) { + this.server = server; + this.logging = new DefaultAutoFetchManagerLogging(serverConfig, this); + + AutofetchConfig autofetchConfig = serverConfig.getAutofetchConfig(); + + useFileLogging = autofetchConfig.isUseFileLogging(); + queryTuning = autofetchConfig.isQueryTuning(); + queryTuningAddVersion = autofetchConfig.isQueryTuningAddVersion(); + profiling = autofetchConfig.isProfiling(); + profilingMin = autofetchConfig.getProfilingMin(); + profilingBase = autofetchConfig.getProfilingBase(); + + setProfilingRate(autofetchConfig.getProfilingRate()); + + + defaultGarbageCollectionWait = (long) autofetchConfig.getGarbageCollectionWait(); + + // determine the mode to use when Query.setAutoFetch() was + // not explicitly set + mode = autofetchConfig.getMode(); + + if (profiling || queryTuning) { + // log the guts of the autoFetch setup + String msg = "AutoFetch queryTuning[" + queryTuning + "] profiling[" + profiling + + "] mode[" + mode + "] profiling rate[" + profilingRate + + "] min[" + profilingMin + "] base[" + profilingBase + "]"; + logging.logToJavaLogger(msg); + } + } + + + + public void clearQueryStatistics() { + server.clearQueryStatistics(); + } + + /** + * Return the number of queries tuned by AutoFetch. + */ + public int getTotalTunedQueryCount(){ + return tunedQueryCount; + } + + /** + * Return the size of the TuneQuery map. + */ + public int getTotalTunedQuerySize(){ + return tunedQueryInfoMap.size(); + } + + /** + * Return the size of the profile map. + */ + public int getTotalProfileSize(){ + return statisticsMap.size(); + } + + public int clearTunedQueryInfo() { + + // reset the rough count as well + tunedQueryCount = 0; + + // clear the map... + int size = tunedQueryInfoMap.size(); + tunedQueryInfoMap.clear(); + return size; + } + + public int clearProfilingInfo() { + int size = statisticsMap.size(); + statisticsMap.clear(); + return size; + } + + + public void serialize() { + + File autoFetchFile = new File(fileName); + + try { + FileOutputStream fout = new FileOutputStream(autoFetchFile); + + ObjectOutputStream oout = new ObjectOutputStream(fout); + oout.writeObject(this); + oout.flush(); + oout.close(); + + } catch (Exception e) { + String msg = "Error serializing autofetch file"; + logging.logError(Level.SEVERE, msg, e); + } + } + + /** + * Return the current Tuned query info for a given origin key. + */ + public TunedQueryInfo getTunedQueryInfo(String originKey) { + return tunedQueryInfoMap.get(originKey); + } + + /** + * Return the current Statistics for a given originKey key. + */ + public Statistics getStatistics(String originKey) { + return statisticsMap.get(originKey); + } + + public Iterator iterateTunedQueryInfo() { + return tunedQueryInfoMap.values().iterator(); + } + + public Iterator iterateStatistics() { + return statisticsMap.values().iterator(); + } + + public boolean isProfiling() { + return profiling; + } + + /** + * When the application is running, BEFORE turning off profiling you + * probably should call collectUsageViaGC() as there is a delay (waiting for + * garbage collection) collecting usage profiling information. + */ + public void setProfiling(boolean profiling) { + this.profiling = profiling; + } + + public boolean isQueryTuning() { + return queryTuning; + } + + public void setQueryTuning(boolean queryTuning) { + this.queryTuning = queryTuning; + } + + public double getProfilingRate() { + return profilingRate; + } + + public AutofetchMode getMode() { + return mode; + } + + public void setMode(AutofetchMode mode) { + this.mode = mode; + } + + public void setProfilingRate(double rate) { + if (rate < 0) { + rate = 0d; + } else if (rate > 1) { + rate = 1d; + } + profilingRate = rate; + } + + public int getProfilingBase() { + return profilingBase; + } + + public void setProfilingBase(int profilingBase) { + this.profilingBase = profilingBase; + } + + public int getProfilingMin() { + return profilingMin; + } + + public void setProfilingMin(int profilingMin) { + this.profilingMin = profilingMin; + } + + /** + * Shutdown the listener. + *

+ * We should try to collect the usage statistics by calling a System.gc(). + * This is necessary for use with short lived applications where garbage + * collection may not otherwise occur at all. + *

+ */ + public void shutdown() { + if (useFileLogging) { + collectUsageViaGC(-1); + serialize(); + } + } + + /** + * Ask for a System.gc() so that we gather node usage information. + *

+ * Really only want to do this sparingly but useful just prior to shutdown + * for short run application where garbage collection may otherwise not + * occur at all. + *

+ *

+ * waitMillis will do a thread sleep to give the garbage collection a little + * time to do its thing assuming we are shutting down the VM. + *

+ *

+ * If waitMillis is -1 then the defaultGarbageCollectionWait is used which + * defaults to 100 milliseconds. + *

+ */ + public String collectUsageViaGC(long waitMillis) { + System.gc(); + try { + if (waitMillis < 0) { + waitMillis = defaultGarbageCollectionWait; + } + Thread.sleep(waitMillis); + } catch (InterruptedException e) { + String msg = "Error while sleeping after System.gc() request."; + logging.logError(Level.SEVERE, msg, e); + return msg; + } + return updateTunedQueryInfo(); + } + + /** + * Update the tuned fetch plans from the current usage information. + */ + public String updateTunedQueryInfo() { + + if (!profiling) { + // we are not collecting any profiling information at + // the moment so don't try updating the tuned query plans. + return "Not profiling"; + } + + synchronized (statisticsMonitor) { + + Counters counters = new Counters(); + + Iterator it = statisticsMap.values().iterator(); + while (it.hasNext()) { + Statistics queryPointStatistics = it.next(); + if (!queryPointStatistics.hasUsage()){ + // no usage statistics collected yet... + counters.incrementNoUsage(); + } else { + updateTunedQueryFromUsage(counters, queryPointStatistics); + } + } + + String summaryInfo = counters.toString(); + + if (counters.isInteresting()){ + // only log it if its interesting + logging.logSummary(summaryInfo); + } + + return summaryInfo; + } + } + + private static class Counters { + + int newPlan; + int modified; + int unchanged; + int noUsage; + + void incrementNoUsage(){ + noUsage++; + } + void incrementNew(){ + newPlan++; + } + void incrementModified(){ + modified++; + } + void incrementUnchanged(){ + unchanged++; + } + boolean isInteresting() { + return newPlan > 0 || modified > 0; + } + public String toString() { + return "new["+newPlan+"] modified["+modified+"] unchanged["+unchanged+"] nousage["+noUsage+"]"; + } + } + + private void updateTunedQueryFromUsage(Counters counters, Statistics statistics) { + + ObjectGraphOrigin queryPoint = statistics.getOrigin(); + String beanType = queryPoint.getBeanType(); + + try { + Class beanClass = ClassUtil.forName(beanType, this.getClass()); + BeanDescriptor beanDescriptor = server.getBeanDescriptor(beanClass); + if (beanDescriptor == null){ + // previously was an entity but not longer + + } else { + // Determine the fetch plan from the latest statistics. + // Use this to compare with current "tuned fetch plan". + OrmQueryDetail newFetchDetail = statistics.buildTunedFetch(beanDescriptor); + + // get the current tuned fetch info... + TunedQueryInfo currentFetch = tunedQueryInfoMap.get(queryPoint.getKey()); + + if (currentFetch == null) { + // its a new fetch plan, add it. + counters.incrementNew(); + + currentFetch = statistics.createTunedFetch(newFetchDetail); + logging.logNew(currentFetch); + tunedQueryInfoMap.put(queryPoint.getKey(), currentFetch); + + } else if (!currentFetch.isSame(newFetchDetail)) { + // the fetch plan has changed, update it. + counters.incrementModified(); + + logging.logChanged(currentFetch, newFetchDetail); + currentFetch.setTunedDetail(newFetchDetail); + + } else { + // the fetch plan has not changed... + counters.incrementUnchanged(); + } + + currentFetch.setProfileCount(statistics.getCounter()); + } + + } catch (ClassNotFoundException e) { + // expected after renaming/moving an entity bean + String msg = e.toString()+" updating autoFetch tuned query for " + beanType + +". It isLikely this bean has been renamed or moved"; + logging.logError(Level.INFO, msg, null); + statisticsMap.remove(statistics.getOrigin().getKey()); + } + } + + /** + * Return true if we should try to use autoFetch for this query. + */ + private boolean useAutoFetch(SpiQuery query) { + + if (query.isLoadBeanCache()){ + // when loading the cache don't tune the query + // as we want full objects loaded into the cache + return false; + } + + Boolean autoFetch = query.isAutofetch(); + if (autoFetch != null) { + // explicitly set... + return autoFetch.booleanValue(); + + } else { + // determine using implicit mode... + switch (mode) { + case DEFAULT_ON: + return true; + + case DEFAULT_OFF: + return false; + + case DEFAULT_ONIFEMPTY: + return query.isDetailEmpty(); + + default: + throw new PersistenceException("Invalid autoFetchMode " + mode); + } + } + } + + /** + * Auto tune the query and enable profiling. + */ + public boolean tuneQuery(SpiQuery query) { + + if (!queryTuning && !profiling) { + return false; + } + + if (!useAutoFetch(query)) { + // not using autoFetch for this query + return false; + } + + ObjectGraphNode parentAutoFetchNode = query.getParentNode(); + if (parentAutoFetchNode != null) { + // This is a +lazy/+query query with profiling on. + // We continue to collect the profiling information. + query.setAutoFetchManager(this); + return true; + } + + // create a query point to identify the query + CallStack stack = server.createCallStack(); + ObjectGraphNode origin = query.setOrigin(stack); + + // get current "tuned fetch" for this query point + TunedQueryInfo tunedFetch = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey()); + + // get the number of times we have collected profiling information + int profileCount = tunedFetch == null ? 0 : tunedFetch.getProfileCount(); + + if (profiling) { + // we want more profiling information? + if (tunedFetch == null) { + query.setAutoFetchManager(this); + + } else if (profileCount < profilingBase) { + query.setAutoFetchManager(this); + + } else if (tunedFetch.isPercentageProfile(profilingRate)) { + query.setAutoFetchManager(this); + } + } + + if (queryTuning) { + if (tunedFetch != null && profileCount >= profilingMin) { + // deemed to have enough profiling + // information for automatic tuning + if (tunedFetch.autoFetchTune(query)){ + // tunedQueryCount++ not thread-safe, could use AtomicInteger. + // But I'm happy if this statistic is a little wrong + // and this is a VERY HOT method + tunedQueryCount++; + } + return true; + } + } + + return false; + } + + /** + * Gather query execution statistics. This could either be the originating + * query in which case the parentNode will be null, or a lazy loading query + * resulting from traversal of the object graph. + */ + public void collectQueryInfo(ObjectGraphNode node, int beans, int micros) { + + if (node != null){ + ObjectGraphOrigin origin = node.getOriginQueryPoint(); + if (origin != null){ + Statistics stats = getQueryPointStats(origin); + stats.collectQueryInfo(node, beans, micros); + } + } + } + + /** + * Collect usage statistics from a node in the object graph. + *

+ * This is sent to use from a EntityBeanIntercept when the finalise method + * is called on the bean. + *

+ */ + public void collectNodeUsage(NodeUsageCollector usageCollector) { + + ObjectGraphOrigin origin = usageCollector.getNode().getOriginQueryPoint(); + + Statistics stats = getQueryPointStats(origin); + + if (logging.isTraceUsageCollection()){ + System.out.println("... NodeUsageCollector "+usageCollector); + } + + stats.collectUsageInfo(usageCollector); + + if (logging.isTraceUsageCollection()){ + System.out.println("stats\n"+stats); + } + } + + private Statistics getQueryPointStats(ObjectGraphOrigin originQueryPoint) { + synchronized (statisticsMonitor) { + Statistics stats = statisticsMap.get(originQueryPoint.getKey()); + if (stats == null) { + stats = new Statistics(originQueryPoint, queryTuningAddVersion); + statisticsMap.put(originQueryPoint.getKey(), stats); + } + return stats; + } + } + + public String toString() { + synchronized (statisticsMonitor) { + return statisticsMap.values().toString(); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java new file mode 100644 index 000000000..f788ad303 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManagerLogging.java @@ -0,0 +1,112 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import java.util.logging.Level; + +import java.util.logging.Logger; + +import com.avaje.ebean.config.AutofetchConfig; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.lib.BackgroundThread; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.transaction.log.SimpleLogger; + +/** + * Handles the logging aspects for the DefaultAutoFetchListener. + *

+ * Note that java util logging loggers generally should not be serialised and + * that is one of the main reasons for pulling out the logging to this class. + *

+ */ +public class DefaultAutoFetchManagerLogging { + + private static final Logger logger = Logger.getLogger(DefaultAutoFetchManagerLogging.class.getName()); + + private final SimpleLogger fileLogger; + + private final DefaultAutoFetchManager manager; + + private final boolean useFileLogger; + + private final boolean traceUsageCollection; + + public DefaultAutoFetchManagerLogging(ServerConfig serverConfig, DefaultAutoFetchManager profileListener) { + + this.manager = profileListener; + + AutofetchConfig autofetchConfig = serverConfig.getAutofetchConfig(); + + traceUsageCollection = GlobalProperties.getBoolean("ebean.autofetch.traceUsageCollection", false); + useFileLogger = autofetchConfig.isUseFileLogging(); + + if (!useFileLogger) { + fileLogger = null; + + } else { + // a separate log file just like the transaction logging + // for putting the profiling log messages. The benefit is that + // this doesn't pollute the main log with heaps of messages. + String baseDir = serverConfig.getLoggingDirectoryWithEval(); + fileLogger = new SimpleLogger(baseDir, "autofetch", true, "csv"); + } + + int updateFreqInSecs = autofetchConfig.getProfileUpdateFrequency(); + + BackgroundThread.add(updateFreqInSecs, new UpdateProfile()); + } + + private final class UpdateProfile implements Runnable { + public void run() { + manager.updateTunedQueryInfo(); + } + } + + public void logError(Level level, String msg, Throwable e) { + if (useFileLogger) { + String errMsg = e == null ? "" : e.getMessage(); + fileLogger.log("\"Error\",\"" + msg+" "+errMsg+"\",,,,"); + } + logger.log(level, msg, e); + } + + public void logToJavaLogger(String msg) { + logger.info(msg); + } + + public void logSummary(String summaryInfo) { + + String msg = "\"Summary\",\""+summaryInfo+"\",,,,"; + + if (useFileLogger) { + fileLogger.log(msg); + } + logger.fine(msg); + } + + public void logChanged(TunedQueryInfo tunedFetch, OrmQueryDetail newQueryDetail) { + + String msg = tunedFetch.getLogOutput(newQueryDetail); + + if (useFileLogger) { + fileLogger.log(msg); + } else { + logger.fine(msg); + } + } + + public void logNew(TunedQueryInfo tunedFetch) { + + String msg = tunedFetch.getLogOutput(null); + + if (useFileLogger) { + fileLogger.log(msg); + } else { + logger.fine(msg); + } + } + + public boolean isTraceUsageCollection() { + return traceUsageCollection; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java new file mode 100644 index 000000000..7aa771f6d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java @@ -0,0 +1,207 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.avaje.ebean.bean.NodeUsageCollector; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebean.meta.MetaAutoFetchStatistic; +import com.avaje.ebean.meta.MetaAutoFetchStatistic.NodeUsageStats; +import com.avaje.ebean.meta.MetaAutoFetchStatistic.QueryStats; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebean.text.PathProperties.Props; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; + +public class Statistics implements Serializable { + + + private static final long serialVersionUID = -5586783791097230766L; + + private final ObjectGraphOrigin origin; + + private final boolean queryTuningAddVersion; + + private int counter; + + private Map queryStatsMap = new LinkedHashMap(); + + private Map nodeUsageMap = new LinkedHashMap(); + + private final String monitor = new String(); + + public Statistics(ObjectGraphOrigin origin, boolean queryTuningAddVersion) { + this.origin = origin; + this.queryTuningAddVersion = queryTuningAddVersion; + } + + public ObjectGraphOrigin getOrigin() { + return origin; + } + + public TunedQueryInfo createTunedFetch(OrmQueryDetail newFetchDetail) { + synchronized (monitor) { + // NB: create a copy of queryPoint allowing garbage + // collection of source... + return new TunedQueryInfo(origin, newFetchDetail, counter); + } + } + + public MetaAutoFetchStatistic createPublicMeta() { + + synchronized (monitor) { + + StatisticsQuery[] sourceQueryStats = queryStatsMap.values().toArray(new StatisticsQuery[queryStatsMap.size()]); + List destQueryStats = new ArrayList(sourceQueryStats.length); + + // copy the query statistics + for (int i = 0; i < sourceQueryStats.length; i++) { + destQueryStats.add(sourceQueryStats[i].createPublicMeta()); + } + + StatisticsNodeUsage[] sourceNodeUsage = nodeUsageMap.values().toArray(new StatisticsNodeUsage[nodeUsageMap.size()]); + List destNodeUsage = new ArrayList(sourceNodeUsage.length); + + // copy the node usage statistics + for (int i = 0; i < sourceNodeUsage.length; i++) { + destNodeUsage.add(sourceNodeUsage[i].createPublicMeta()); + } + + return new MetaAutoFetchStatistic(origin, counter, destQueryStats, destNodeUsage); + } + } + + /** + * Return the number of times the root query has executed. + *

+ * This tells us how much profiling we have done for this query. + * For example, after 100 times we may stop collecting more profiling info. + *

+ */ + public int getCounter() { + return counter; + } + + /** + * Return true if this has usage statistics. + */ + public boolean hasUsage() { + synchronized (monitor) { + return !nodeUsageMap.isEmpty(); + } + } + + public OrmQueryDetail buildTunedFetch(BeanDescriptor rootDesc){ + + synchronized (monitor) { + if (nodeUsageMap.isEmpty()){ + return null; + } + + PathProperties pathProps = new PathProperties(); + + Iterator it = nodeUsageMap.values().iterator(); + while (it.hasNext()) { + StatisticsNodeUsage statsNode = it.next(); + statsNode.buildTunedFetch(pathProps, rootDesc); + } + + OrmQueryDetail detail = new OrmQueryDetail(); + + Collection pathProperties = pathProps.getPathProps(); + for (Props props : pathProperties) { + if (!props.isEmpty()){ + detail.addFetch(props.getPath(), props.getPropertiesAsString(), null); + } + } + + detail.sortFetchPaths(rootDesc); + return detail; + } + } + + + public void collectQueryInfo(ObjectGraphNode node, int beansLoaded, int micros) { + + synchronized (monitor) { + String key = node.getPath(); + if (key == null){ + key = ""; + // this is basically the number of times the root query + // has executed which gives us an indication of how + // much profiling information we have gathered. + counter++; + } + + StatisticsQuery stats = queryStatsMap.get(key); + if (stats == null){ + stats = new StatisticsQuery(key); + queryStatsMap.put(key, stats); + } + stats.add(beansLoaded, micros); + } + } + + + /** + * Collect the usage information for from a instance for this node. + */ + public void collectUsageInfo(NodeUsageCollector profile) { + + if (profile.isEmpty()){ + // no usage was collected + } else { + ObjectGraphNode node = profile.getNode(); + + StatisticsNodeUsage nodeStats = getNodeStats(node.getPath()); + nodeStats.publish(profile); + } + } + + private StatisticsNodeUsage getNodeStats(String path) { + + synchronized (monitor) { + StatisticsNodeUsage nodeStats = nodeUsageMap.get(path); + if (nodeStats == null) { + nodeStats = new StatisticsNodeUsage(path, queryTuningAddVersion); + nodeUsageMap.put(path, nodeStats); + } + return nodeStats; + } + } + + public String getUsageDebug() { + synchronized (monitor) { + StringBuilder sb = new StringBuilder(); + sb.append("root[").append(origin.getBeanType()).append("] "); + for (StatisticsNodeUsage node : nodeUsageMap.values()) { + sb.append(node.toString()).append("\n"); + } + return sb.toString(); + } + } + + public String getQueryStatDebug() { + synchronized (monitor) { + StringBuilder sb = new StringBuilder(); + for (StatisticsQuery queryStat : queryStatsMap.values()) { + sb.append(queryStat.toString()).append("\n"); + } + return sb.toString(); + } + } + + public String toString() { + + synchronized (monitor) { + return getUsageDebug(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsNodeUsage.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsNodeUsage.java new file mode 100644 index 000000000..05741bae5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsNodeUsage.java @@ -0,0 +1,123 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.logging.Logger; + +import com.avaje.ebean.bean.NodeUsageCollector; +import com.avaje.ebean.meta.MetaAutoFetchStatistic.NodeUsageStats; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.query.SplitName; + +/** + * Collects usages statistics for a given node in the object graph. + */ +public class StatisticsNodeUsage implements Serializable { + + private static final long serialVersionUID = -1663951463963779547L; + + private static final Logger logger = Logger.getLogger(StatisticsNodeUsage.class.getName()); + + private final String monitor = new String(); + + private final String path; + + private final boolean queryTuningAddVersion; + + private int profileCount; + + private int profileUsedCount; + + private boolean modified; + + private Set aggregateUsed = new LinkedHashSet(); + + public StatisticsNodeUsage(String path, boolean queryTuningAddVersion) { + this.path = path; + this.queryTuningAddVersion = queryTuningAddVersion; + } + + public NodeUsageStats createPublicMeta() { + synchronized(monitor){ + String[] usedProps = aggregateUsed.toArray(new String[aggregateUsed.size()]); + return new NodeUsageStats(path, profileCount, profileUsedCount, usedProps); + } + } + + public void buildTunedFetch(PathProperties pathProps, BeanDescriptor rootDesc) { + + synchronized(monitor){ + + BeanDescriptor desc = rootDesc; + if (path != null){ + ElPropertyValue elGetValue = rootDesc.getElGetValue(path); + if (elGetValue == null){ + desc = null; + logger.warning("Autofetch: Can't find join for path["+path+"] for "+rootDesc.getName()); + + } else { + BeanProperty beanProperty = elGetValue.getBeanProperty(); + if (beanProperty instanceof BeanPropertyAssoc){ + desc = ((BeanPropertyAssoc) beanProperty).getTargetDescriptor(); + } + } + } + + for (String propName : aggregateUsed) { + BeanProperty beanProp = desc.getBeanPropertyFromPath(propName); + if (beanProp == null){ + logger.warning("Autofetch: Can't find property["+propName+"] for "+desc.getName()); + + } else { + if (beanProp instanceof BeanPropertyAssoc){ + BeanPropertyAssoc assocProp = (BeanPropertyAssoc)beanProp; + String targetIdProp = assocProp.getTargetIdProperty(); + String manyPath = SplitName.add(path, assocProp.getName()); + pathProps.addToPath(manyPath, targetIdProp); + } else { + if (beanProp.isLob() && !beanProp.isFetchEager()) { + // AutoFetch will not include Lob's marked FetchLazy + // (which is the default for Lob's so typical). + } else { + pathProps.addToPath(path, beanProp.getName()); + } + } + } + } + + if ((modified || queryTuningAddVersion) && desc != null) { + BeanProperty[] versionProps = desc.propertiesVersion(); + if (versionProps.length > 0) { + pathProps.addToPath(path, versionProps[0].getName()); + } + } + } + } + + public void publish(NodeUsageCollector profile) { + + synchronized(monitor){ + + HashSet used = profile.getUsed(); + + profileCount++; + if (!used.isEmpty()){ + profileUsedCount++; + aggregateUsed.addAll(used); + } + if (profile.isModified()){ + modified = true; + } + } + } + + public String toString() { + return "path["+path+"] profileCount["+profileCount+"] used["+profileUsedCount+"] props"+aggregateUsed; + } +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java new file mode 100644 index 000000000..d0b4bbd03 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java @@ -0,0 +1,42 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import java.io.Serializable; + +import com.avaje.ebean.meta.MetaAutoFetchStatistic.QueryStats; + +/** + * Used to accumulate query execution statistics. + */ +public class StatisticsQuery implements Serializable { + + private static final long serialVersionUID = -1133958958072778811L; + + private final String path; + + private int exeCount; + + private int totalBeanLoaded; + + private int totalMicros; + + public StatisticsQuery(String path){ + this.path = path; + } + + public QueryStats createPublicMeta() { + return new QueryStats(path, exeCount, totalBeanLoaded, totalMicros); + } + + public void add(int beansLoaded, int micros) { + exeCount++; + totalBeanLoaded += beansLoaded; + totalMicros += micros; + } + + public String toString() { + long avgMicros = exeCount == 0 ? 0 : totalMicros / exeCount; + + return "queryExe path["+path+"] count[" + exeCount + "] totalBeansLoaded[" + totalBeanLoaded + "] avgMicros[" + + avgMicros + "] totalMicros[" + totalMicros + "]"; + } +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java new file mode 100644 index 000000000..1a5dc9586 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/TunedQueryInfo.java @@ -0,0 +1,193 @@ +package com.avaje.ebeaninternal.server.autofetch; + +import java.io.Serializable; + +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebean.meta.MetaAutoFetchTunedQueryInfo; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; + +/** + * Holds tuned query information. Is immutable so this represents the tuning at + * a given point in time. + */ +public class TunedQueryInfo implements Serializable { + + private static final long serialVersionUID = 7381493228797997282L; + + private final ObjectGraphOrigin origin; + + /** + * The tuned query details with joins and properties. + */ + private OrmQueryDetail tunedDetail; + + /** + * The number of times profiling has been collected for this query point. + */ + private int profileCount; + + private Long lastTuneTime = Long.valueOf(0); + + private final String rateMonitor = new String(); + + /** + * The number of queries tuned by this object. + * Could use AtomicInteger perhaps. + */ + private transient int tunedCount; + + private transient int rateTotal; + + private transient int rateHits; + + private transient double lastRate; + + public TunedQueryInfo(ObjectGraphOrigin queryPoint, OrmQueryDetail tunedDetail, int profileCount) { + this.origin = queryPoint; + this.tunedDetail = tunedDetail; + this.profileCount = profileCount; + } + + /** + * Return true if this query should be profiled based on a percentage rate. + */ + public boolean isPercentageProfile(double rate) { + + synchronized (rateMonitor) { + + if (lastRate != rate) { + // the rate has changed so resetting + lastRate = rate; + rateTotal = 0; + rateHits = 0; + } + + rateTotal++; + if (rate > (double) rateHits / rateTotal) { + rateHits++; + return true; + } else { + return false; + } + } + } + + /** + * Create a copy of this tuned fetch data for public consumption. + */ + public MetaAutoFetchTunedQueryInfo createPublicMeta() { + return new MetaAutoFetchTunedQueryInfo(origin, tunedDetail.toString(), profileCount, tunedCount, lastTuneTime); + } + + /** + * Set the number of times profiling has been collected for this query + * point. + */ + public void setProfileCount(int profileCount) { + // int assignment is atomic + this.profileCount = profileCount; + } + + /** + * Set the tuned query detail. + */ + public void setTunedDetail(OrmQueryDetail tunedDetail) { + // assignment is atomic + this.tunedDetail = tunedDetail; + this.lastTuneTime = Long.valueOf(System.currentTimeMillis()); + } + + /** + * Return true if the fetches are essentially the same. + */ + public boolean isSame(OrmQueryDetail newQueryDetail) { + if (tunedDetail == null) { + return false; + } + return tunedDetail.isAutoFetchEqual(newQueryDetail); + } + + /** + * Tune the query by replacing its OrmQueryDetail with a tuned one. + * + * @return true if the query was tuned, otherwise false. + */ + public boolean autoFetchTune(SpiQuery query) { + if (tunedDetail == null) { + return false; + } + + boolean tuned = false; + //Note: tunedDetail is immutable by convention + if (query.isDetailEmpty()) { + tuned = true; + // tune by 'replacement' + query.setDetail(tunedDetail.copy()); + } else { + // tune by 'addition' + tuned = query.tuneFetchProperties(tunedDetail); + } + if (tuned){ + query.setAutoFetchTuned(true); + // a case for AtomicInteger but good enough for statistics + tunedCount++; + } + return tuned; + } + + /** + * Return the time of the last tune. + */ + public Long getLastTuneTime() { + return lastTuneTime; + } + + /** + * Return the number of queries tuned by this object. + */ + public int getTunedCount() { + return tunedCount; + } + + /** + * Return the number of times profiling has been collected for this query + * point. + */ + public int getProfileCount() { + return profileCount; + } + + public OrmQueryDetail getTunedDetail() { + return tunedDetail; + } + + public ObjectGraphOrigin getOrigin() { + return origin; + } + + public String getLogOutput(OrmQueryDetail newQueryDetail) { + + boolean changed = newQueryDetail != null; + + StringBuilder sb = new StringBuilder(150); + sb.append( changed ? "\"Changed\",":"\"New\","); + sb.append("\"").append(origin.getBeanType()).append("\","); + sb.append("\"").append(origin.getKey()).append("\","); + if (changed){ + sb.append("\"to: ").append(newQueryDetail.toString()).append("\","); + sb.append("\"from: ").append(tunedDetail.toString()).append("\","); + } else { + sb.append("\"to: ").append(tunedDetail.toString()).append("\","); + sb.append("\"\","); + } + sb.append("\"").append(origin.getFirstStackElement()).append("\""); + + return sb.toString(); + } + + public String toString() { + return origin.getBeanType()+" "+origin.getKey()+" " + tunedDetail; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/package.html b/src/main/java/com/avaje/ebeaninternal/server/autofetch/package.html new file mode 100644 index 000000000..3b9b20417 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/package.html @@ -0,0 +1,8 @@ + + +AutoFetch Implementation + + +AutoFetch Implementation + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchStatisticFinder.java b/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchStatisticFinder.java new file mode 100644 index 000000000..6bf429f7d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchStatisticFinder.java @@ -0,0 +1,79 @@ +package com.avaje.ebeaninternal.server.bean; + +import java.util.Iterator; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.common.BeanList; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebean.meta.MetaAutoFetchStatistic; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.autofetch.Statistics; + +/** + * Bean Finder for MetaAutoFetchStatistic. + *

+ * This gets the meta data from the AutoFetchManager and creates a copy of that + * data to give back to the caller in the form of MetaAutoFetchStatistic beans. + *

+ */ +public class BFAutoFetchStatisticFinder implements BeanFinder { + + + public MetaAutoFetchStatistic find(BeanQueryRequest request) { + SpiQuery query = (SpiQuery)request.getQuery(); + try { + String queryPointKey = (String) query.getId(); + + SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); + AutoFetchManager manager = server.getAutoFetchManager(); + + Statistics stats = manager.getStatistics(queryPointKey); + if (stats != null) { + return stats.createPublicMeta(); + } else { + return null; + } + + } catch (Exception e) { + throw new PersistenceException(e); + } + } + + /** + * Only returns Lists at this stage. + */ + public BeanCollection findMany(BeanQueryRequest request) { + + SpiQuery.Type queryType = ((SpiQuery)request.getQuery()).getType(); + if (!queryType.equals(SpiQuery.Type.LIST)) { + throw new PersistenceException("Only findList() supported at this stage."); + } + + SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); + AutoFetchManager manager = server.getAutoFetchManager(); + + BeanList list = new BeanList(); + + Iterator it = manager.iterateStatistics(); + while (it.hasNext()) { + Statistics stats = it.next(); + // create a copy for public use + list.add(stats.createPublicMeta()); + } + + String orderBy = request.getQuery().order().toStringFormat(); + if (orderBy == null){ + orderBy = "beanType"; + } + server.sort(list, orderBy); + + + return list; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchTunedFetchFinder.java b/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchTunedFetchFinder.java new file mode 100644 index 000000000..598512b09 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchTunedFetchFinder.java @@ -0,0 +1,76 @@ +package com.avaje.ebeaninternal.server.bean; + +import java.util.Iterator; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.common.BeanList; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebean.meta.MetaAutoFetchTunedQueryInfo; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.autofetch.TunedQueryInfo; + +/** + * BeanFinder for MetaAutoFetchTunedFetch. + */ +public class BFAutoFetchTunedFetchFinder implements BeanFinder { + + + public MetaAutoFetchTunedQueryInfo find(BeanQueryRequest request) { + + SpiQuery query = (SpiQuery)request.getQuery(); + try { + String queryPointKey = (String)query.getId(); + + SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); + AutoFetchManager manager = server.getAutoFetchManager(); + + TunedQueryInfo tunedFetch = manager.getTunedQueryInfo(queryPointKey); + if (tunedFetch != null){ + return tunedFetch.createPublicMeta(); + } else { + return null; + } + + } catch (Exception e){ + throw new PersistenceException(e); + } + } + + /** + * Only returns Lists at this stage. + */ + public BeanCollection findMany(BeanQueryRequest request) { + + SpiQuery.Type queryType = ((SpiQuery)request.getQuery()).getType(); + if (!queryType.equals(SpiQuery.Type.LIST)){ + throw new PersistenceException("Only findList() supported at this stage."); + } + + SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); + AutoFetchManager manager = server.getAutoFetchManager(); + + BeanList list = new BeanList(); + + Iterator it = manager.iterateTunedQueryInfo(); + while (it.hasNext()) { + TunedQueryInfo tunedFetch = it.next(); + // create a copy for public use + list.add(tunedFetch.createPublicMeta()); + } + + String orderBy = request.getQuery().order().toStringFormat(); + if (orderBy == null){ + orderBy = "beanType, origQueryPlanHash"; + } + server.sort(list, orderBy); + + + return list; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/BFQueryStatisticFinder.java b/src/main/java/com/avaje/ebeaninternal/server/bean/BFQueryStatisticFinder.java new file mode 100644 index 000000000..b87df5eb7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/bean/BFQueryStatisticFinder.java @@ -0,0 +1,69 @@ +package com.avaje.ebeaninternal.server.bean; + +import java.util.Iterator; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.common.BeanList; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebean.meta.MetaQueryStatistic; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.query.CQueryPlan; + +/** + * BeanFinder for MetaQueryStatistic. + */ +public class BFQueryStatisticFinder implements BeanFinder { + + + public MetaQueryStatistic find(BeanQueryRequest request) { + throw new RuntimeException("Not Supported yet"); + } + + /** + * Only returns Lists at this stage. + */ + public BeanCollection findMany(BeanQueryRequest request) { + + SpiQuery.Type queryType = ((SpiQuery)request.getQuery()).getType(); + if (!queryType.equals(SpiQuery.Type.LIST)){ + throw new PersistenceException("Only findList() supported at this stage."); + } + + BeanList list = new BeanList(); + + SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); + build(list, server); + + String orderBy = request.getQuery().order().toStringFormat(); + if (orderBy == null){ + orderBy = "beanType, origQueryPlanHash, autofetchTuned"; + } + server.sort(list, orderBy); + + return list; + } + + private void build(List list, SpiEbeanServer server) { + + for (BeanDescriptor desc : server.getBeanDescriptors()) { + desc.clearQueryStatistics(); + build(list, desc); + } + } + + private void build(List list, BeanDescriptor desc) { + + Iterator it = desc.queryPlans(); + while (it.hasNext()) { + CQueryPlan queryPlan = (CQueryPlan) it.next(); + list.add(queryPlan.createMetaQueryStatistic(desc.getFullName())); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/package.html b/src/main/java/com/avaje/ebeaninternal/server/bean/package.html new file mode 100644 index 000000000..f77109f52 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/bean/package.html @@ -0,0 +1,8 @@ + + +BeanFinders, BeanControllers etc for "meta" beans + + +BeanFinders, BeanControllers etc for "meta" beans + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java new file mode 100644 index 000000000..53ebf05a1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.cache; + +import java.util.Set; + +public class CachedBeanData { + + private final Object sharableBean; + private final Set loadedProperties; + private final Object[] data; + private final int naturalKeyUpdate; + + public CachedBeanData(Object sharableBean, Set loadedProperties, Object[] data, int naturalKeyUpdate) { + this.sharableBean = sharableBean; + this.loadedProperties= loadedProperties; + this.data = data; + this.naturalKeyUpdate = naturalKeyUpdate; + } + + public Object getSharableBean() { + return sharableBean; + } + + public boolean isNaturalKeyUpdate() { + return naturalKeyUpdate > -1; + } + + public Object getNaturalKey() { + return data[naturalKeyUpdate]; + } + + public boolean containsProperty(String propName) { + return loadedProperties == null || loadedProperties.contains(propName); + } + + public Object getData(int i){ + return data[i]; + } + + public Set getLoadedProperties() { + return loadedProperties; + } + + public Object[] copyData() { + Object[] dest = new Object[data.length]; + System.arraycopy(data, 0, dest, 0, data.length); + return dest; + } + +} + diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java new file mode 100644 index 000000000..0390ffb78 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java @@ -0,0 +1,103 @@ +package com.avaje.ebeaninternal.server.cache; + +import java.util.HashSet; +import java.util.Set; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +public class CachedBeanDataFromBean { + + private final BeanDescriptor desc; + private final Object bean; + private final EntityBeanIntercept ebi; + + private final Set loadedProps; + private final Set extractProps; + + public static CachedBeanData extract(BeanDescriptor desc, Object bean){ + if (bean instanceof EntityBean){ + return new CachedBeanDataFromBean(desc, bean, ((EntityBean)bean)._ebean_getIntercept()).extract(); + + } else { + return new CachedBeanDataFromBean(desc, bean, null).extract(); + } + } + + public static CachedBeanData extract(BeanDescriptor desc, Object bean, EntityBeanIntercept ebi){ + return new CachedBeanDataFromBean(desc, bean, ebi).extract(); + } + + private CachedBeanDataFromBean(BeanDescriptor desc, Object bean, EntityBeanIntercept ebi) { + this.desc = desc; + this.bean = bean; + this.ebi = ebi; + if (ebi != null){ + this.loadedProps = ebi.getLoadedProps(); + this.extractProps = (loadedProps == null) ? null : new HashSet(); + } else { + this.extractProps = new HashSet(); + this.loadedProps = null; + } + } + + private CachedBeanData extract(){ + + BeanProperty[] props = desc.propertiesNonMany(); + + Object[] data = new Object[props.length]; + + int naturalKeyUpdate = -1; + for (int i = 0; i < props.length; i++) { + BeanProperty prop = props[i]; + if (includeNonManyProperty(prop.getName())){ + + data[i] = prop.getCacheDataValue(bean); + if (prop.isNaturalKey()) { + naturalKeyUpdate = i; + } + if (ebi != null){ + if (extractProps != null){ + extractProps.add(prop.getName()); + } + } else if (data[i] != null){ + if (extractProps != null){ + extractProps.add(prop.getName()); + } + } + } + } + + Object sharableBean = null; + if (desc.isCacheSharableBeans() && ebi != null && loadedProps == null){ + if (ebi.isReadOnly()){ + sharableBean = bean; + } else { + // create a readOnly sharable instance by copying the data + sharableBean = desc.createBean(false); + BeanProperty[] propertiesId = desc.propertiesId(); + for (int i = 0; i < propertiesId.length; i++) { + Object v = propertiesId[i].getValue(bean); + propertiesId[i].setValue(sharableBean, v); + } + BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient(); + for (int i = 0; i < propertiesNonTransient.length; i++) { + Object v = propertiesNonTransient[i].getValue(bean); + propertiesNonTransient[i].setValue(sharableBean, v); + } + EntityBeanIntercept ebi = ((EntityBean)sharableBean)._ebean_intercept(); + ebi.setReadOnly(true); + ebi.setLoaded(); + } + } + + return new CachedBeanData(sharableBean, extractProps, data, naturalKeyUpdate); + } + + private boolean includeNonManyProperty(String name) { + return loadedProps == null || loadedProps.contains(name); + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java new file mode 100644 index 000000000..5199d8375 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java @@ -0,0 +1,117 @@ +package com.avaje.ebeaninternal.server.cache; + +import java.util.HashSet; +import java.util.Set; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; + +public class CachedBeanDataToBean { + + private final BeanDescriptor desc; + private final Object bean; + private final EntityBeanIntercept ebi; + private final CachedBeanData cacheBeandata; + private final Set cacheLoadedProperties; + private final Set loadedProps; + + private final Set excludeProps; + private final Object oldValuesBean; + private final boolean readOnly; + + public static void load(BeanDescriptor desc, Object bean, CachedBeanData cacheBeandata) { + if (bean instanceof EntityBean){ + load(desc, bean, ((EntityBean)bean)._ebean_getIntercept(), cacheBeandata); + } else { + load(desc, bean, null, cacheBeandata); + } + } + + public static void load(BeanDescriptor desc, Object bean, EntityBeanIntercept ebi, CachedBeanData cacheBeandata) { + new CachedBeanDataToBean(desc, bean, ebi, cacheBeandata).load(); + } + + private CachedBeanDataToBean(BeanDescriptor desc, Object bean, EntityBeanIntercept ebi, CachedBeanData cacheBeandata) { + this.desc = desc; + this.bean = bean; + this.ebi = ebi; + this.cacheBeandata = cacheBeandata; + this.cacheLoadedProperties = cacheBeandata.getLoadedProperties(); + this.loadedProps = (cacheLoadedProperties == null) ? null : new HashSet(); + + if (ebi != null){ + this.excludeProps = ebi.getLoadedProps(); + this.oldValuesBean = ebi.getOldValues(); + this.readOnly = ebi.isReadOnly(); + } else { + this.excludeProps = null; + this.oldValuesBean = null; + this.readOnly = false; + } + } + + private boolean load(){ + + BeanProperty[] propertiesNonTransient = desc.propertiesNonMany(); + for (int i = 0; i < propertiesNonTransient.length; i++) { + BeanProperty prop = propertiesNonTransient[i]; + if (includeNonManyProperty(prop.getName())){ + Object data = cacheBeandata.getData(i); + prop.setCacheDataValue(bean, data, oldValuesBean, readOnly); + } + } + BeanPropertyAssocMany[] manys = desc.propertiesMany(); + for (int i = 0; i < manys.length; i++) { + BeanPropertyAssocMany prop = manys[i]; + if (includeManyProperty(prop.getName())){ + // set a lazy loading proxy + prop.createReference(bean); + } + } + + if (ebi != null){ + if (loadedProps == null){ + ebi.setLoadedProps(null); + } else { + HashSet mergeProps = new HashSet(); + if (excludeProps != null) { + mergeProps.addAll(excludeProps); + } + mergeProps.addAll(loadedProps); + ebi.setLoadedProps(mergeProps); + } + ebi.setLoadedLazy(); + } + + return true; + } + + private boolean includeManyProperty(String name) { + if (excludeProps != null && excludeProps.contains(name)){ + // ignore this property (partial bean lazy loading) + return false; + } + if (loadedProps != null){ + loadedProps.add(name); + } + return true; + } + + private boolean includeNonManyProperty(String name) { + if (excludeProps != null && excludeProps.contains(name)){ + // ignore this property (partial bean lazy loading) + return false; + } + if (cacheLoadedProperties != null && !cacheLoadedProperties.contains(name)){ + return false; + } + if (loadedProps != null){ + loadedProps.add(name); + } + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataUpdate.java new file mode 100644 index 000000000..93c883213 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataUpdate.java @@ -0,0 +1,49 @@ +package com.avaje.ebeaninternal.server.cache; + +import java.util.HashSet; +import java.util.Set; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +public class CachedBeanDataUpdate { + + public static CachedBeanData update(BeanDescriptor desc, CachedBeanData data, PersistRequestBean updateRequest){ + + + Set loadedProperties = data.getLoadedProperties(); + Object[] copyOfData = data.copyData(); + + Object updateBean = updateRequest.getBean(); + Set updatedProperties = updateRequest.getUpdatedProperties(); + + int naturalKeyUpdate = -1; + boolean mergeProperties = false; + BeanProperty[] props = desc.propertiesNonMany(); + for (int i = 0; i < props.length; i++) { + if (updatedProperties.contains(props[i].getName())){ + if (props[i].isNaturalKey()){ + naturalKeyUpdate = i; + } + copyOfData[i] = props[i].getCacheDataValue(updateBean); + if (loadedProperties != null && !mergeProperties && !loadedProperties.contains(props[i].getName())){ + mergeProperties = true; + } + } + } + + if (mergeProperties){ + HashSet mergeProps = new HashSet(); + mergeProps.addAll(loadedProperties); + mergeProps.addAll(updatedProperties); + loadedProperties = mergeProps; + } + + return new CachedBeanData(null, loadedProperties, copyOfData, naturalKeyUpdate); + + } + + + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedManyIds.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedManyIds.java new file mode 100644 index 000000000..3f123efa7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedManyIds.java @@ -0,0 +1,17 @@ +package com.avaje.ebeaninternal.server.cache; + +import java.util.List; + +public class CachedManyIds { + + private final List idList; + + public CachedManyIds(List idList) { + this.idList = idList; + } + + public List getIdList() { + return idList; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java new file mode 100644 index 000000000..8318c56bd --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java @@ -0,0 +1,123 @@ +package com.avaje.ebeaninternal.server.cache; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + +import com.avaje.ebean.annotation.CacheTuning; +import com.avaje.ebean.cache.ServerCache; +import com.avaje.ebean.cache.ServerCacheFactory; +import com.avaje.ebean.cache.ServerCacheOptions; + +/** + * Manages the construction of caches. + */ +public class DefaultCacheHolder { + + private final ConcurrentHashMap concMap = new ConcurrentHashMap(); + + private final HashMap synchMap = new HashMap(); + + private final Object monitor = new Object(); + + private final ServerCacheFactory cacheFactory; + + private final ServerCacheOptions defaultOptions; + + private final boolean useBeanTuning; + + /** + * Create with a cache factory and default cache options. + * + * @param cacheFactory + * the factory for creating the cache + * @param defaultOptions + * the default options for tuning the cache + * @param useBeanTuning + * if true then use the bean class specific tuning. This is + * generally false for the query cache. + */ + public DefaultCacheHolder(ServerCacheFactory cacheFactory, + ServerCacheOptions defaultOptions, boolean useBeanTuning) { + + this.cacheFactory = cacheFactory; + this.defaultOptions = defaultOptions; + this.useBeanTuning = useBeanTuning; + } + + /** + * Return the default cache options. + */ + public ServerCacheOptions getDefaultOptions() { + return defaultOptions; + } + + /** + * Return the cache for a given bean type. + */ + public ServerCache getCache(String cacheKey) { + + ServerCache cache = concMap.get(cacheKey); + if (cache != null) { + return cache; + } + synchronized (monitor) { + cache = synchMap.get(cacheKey); + if (cache == null) { + ServerCacheOptions options = getCacheOptions(cacheKey); + cache = cacheFactory.createCache(cacheKey, options); + synchMap.put(cacheKey, cache); + concMap.put(cacheKey, cache); + } + return cache; + } + } + + public void clearCache(String cacheKey) { + + ServerCache cache = concMap.get(cacheKey); + if (cache != null) { + cache.clear(); + } + } + + /** + * Return true if there is an active cache for this bean type. + */ + public boolean isCaching(String beanType) { + return concMap.containsKey(beanType); + } + + public void clearAll() { + Iterator it = concMap.values().iterator(); + while (it.hasNext()) { + ServerCache serverCache = it.next(); + serverCache.clear(); + } + } + + /** + * Return the cache options for a given bean type. + */ + private ServerCacheOptions getCacheOptions(String beanType) { + + if (useBeanTuning) { + // read the deployment annotation + try { + Class cls = Class.forName(beanType); + CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class); + if (cacheTuning != null) { + ServerCacheOptions o = new ServerCacheOptions(cacheTuning); + o.applyDefaults(defaultOptions); + return o; + } + } catch (ClassNotFoundException e){ + // ignore + } + } + + return defaultOptions.copy(); + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java new file mode 100644 index 000000000..a712a2ce7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java @@ -0,0 +1,407 @@ +package com.avaje.ebeaninternal.server.cache; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.cache.ServerCache; +import com.avaje.ebean.cache.ServerCacheOptions; +import com.avaje.ebean.cache.ServerCacheStatistics; + + +/** + * The default cache implementation. + *

+ * It is base on ConcurrentHashMap with periodic trimming using a TimerTask. + * The periodic trimming means that an LRU list does not have to be maintained. + *

+ */ +public class DefaultServerCache implements ServerCache { + + private static final Logger logger = Logger.getLogger(DefaultServerCache.class.getName()); + + private static final CacheEntryComparator comparator = new CacheEntryComparator(); + + private final ConcurrentHashMap map = new ConcurrentHashMap(); + + private final AtomicInteger missCount = new AtomicInteger(); + + private final AtomicInteger removedHitCount = new AtomicInteger(); + + private final Object monitor = new Object(); + + private final String name; + + private int maxSize; + + private long trimFrequency; + + private int maxIdleSecs; + + private int maxSecsToLive; + + public DefaultServerCache(String name, ServerCacheOptions options) { + this(name, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive()); + } + + public DefaultServerCache(String name, int maxSize, int maxIdleSecs, int maxSecsToLive) { + this.name = name; + this.maxSize = maxSize; + this.maxIdleSecs = maxIdleSecs; + this.maxSecsToLive = maxSecsToLive; + this.trimFrequency = 60; + + } + + public void init(EbeanServer server) { + + TrimTask trim = new TrimTask(); + + BackgroundExecutor executor = server.getBackgroundExecutor(); + executor.executePeriodically(trim, trimFrequency, TimeUnit.SECONDS); + } + + + + + public ServerCacheStatistics getStatistics(boolean reset) { + + ServerCacheStatistics s = new ServerCacheStatistics(); + s.setCacheName(name); + s.setMaxSize(maxSize); + + // these counters won't necessarily be consistent with + // respect to each other as activity can occur while + // they are being calculated + int mc = reset ? missCount.getAndSet(0) : missCount.get(); + int hc = getHitCount(reset); + int size = size(); + + s.setSize(size); + s.setHitCount(hc); + s.setMissCount(mc); + + return s; + } + + public int getHitRatio() { + + int mc = missCount.get(); + int hc = getHitCount(false); + + int totalCount = hc + mc; + if (totalCount == 0){ + return 0; + } else { + return hc * 100 / totalCount; + } + + } + + private int getHitCount(boolean reset) { + + int hc = reset ? removedHitCount.getAndSet(0) : removedHitCount.get(); + + Iterator it = map.values().iterator(); + while (it.hasNext()) { + CacheEntry cacheEntry = it.next(); + hc += cacheEntry.getHitCount(reset); + } + + return hc; + } + + + public ServerCacheOptions getOptions() { + synchronized (monitor) { + ServerCacheOptions o = new ServerCacheOptions(); + o.setMaxIdleSecs(maxIdleSecs); + o.setMaxSize(maxSize); + o.setMaxSecsToLive(maxSecsToLive); + return o; + } + } + + public void setOptions(ServerCacheOptions o) { + synchronized (monitor) { + maxIdleSecs = o.getMaxIdleSecs(); + maxSize = o.getMaxSize(); + maxSecsToLive = o.getMaxSecsToLive(); + } + } + + + /** + * Return the max cache size. + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Set the max cache size. + */ + public void setMaxSize(int maxSize) { + synchronized (monitor) { + this.maxSize = maxSize; + } + } + + /** + * Return the max idle time. + */ + public long getMaxIdleSecs() { + return maxIdleSecs; + } + + /** + * Set the max idle time. + */ + public void setMaxIdleSecs(int maxIdleSecs) { + synchronized (monitor) { + this.maxIdleSecs = maxIdleSecs; + } + } + + /** + * Return the maximum time to live. + */ + public long getMaxSecsToLive() { + return maxSecsToLive; + } + + /** + * Set the maximum time to live. + */ + public void setMaxSecsToLive(int maxSecsToLive) { + synchronized (monitor) { + this.maxSecsToLive = maxSecsToLive; + } + } + + /** + * Return the name of the cache. + */ + public String getName() { + return name; + } + + /** + * Clear the cache. + */ + public void clear() { + map.clear(); + } + + /** + * Return a value from the cache. + */ + public Object get(Object key) { + + CacheEntry entry = map.get(key); + + if (entry == null){ + missCount.incrementAndGet(); + return null; + + } else { + // get value incrementing last + // access time and hitCount + return entry.getValue(); + } + } + + /** + * Put a value into the cache. + */ + public Object put(Object key, Object value) { + // put new entry with create time + CacheEntry entry = map.put(key, new CacheEntry(key, value)); + if (entry == null){ + return null; + } else { + int removedHits = entry.getHitCount(true); + removedHitCount.addAndGet(removedHits); + return entry.getValue(); + } + } + + /** + * Put a value into the cache but only if absent. + */ + public Object putIfAbsent(Object key, Object value) { + CacheEntry entry = map.putIfAbsent(key, new CacheEntry(key, value)); + if (entry == null){ + return null; + } else { + return entry.getValue(); + } + } + + /** + * Remove an entry from the cache. + */ + public Object remove(Object key) { + CacheEntry entry = map.remove(key); + if (entry == null){ + return null; + } else { + int removedHits = entry.getHitCount(true); + removedHitCount.addAndGet(removedHits); + return entry.getValue(); + } + } + + /** + * Return the number of elements in the cache. + */ + public int size() { + return map.size(); + } + + private Iterator cacheEntries() { + return map.values().iterator(); + } + + /** + * The task used to periodically trim the cache. + */ + private class TrimTask implements Runnable { + + public void run() { + + long startTime = System.currentTimeMillis(); + + if (logger.isLoggable(Level.FINER)){ + logger.finer("trimming cache " + name); + } + + int trimmedByIdle = 0; + int trimmedByTTL = 0; + int trimmedByLRU = 0; + + boolean trimMaxSize = maxSize > 0 && maxSize < size(); + + ArrayList activeList = new ArrayList(); + + long idleExpire = System.currentTimeMillis() - (maxIdleSecs*1000); + long ttlExpire = System.currentTimeMillis() - (maxSecsToLive*1000); + + Iterator it = cacheEntries(); + while (it.hasNext()) { + CacheEntry cacheEntry = it.next(); + if (maxIdleSecs > 0 && idleExpire > cacheEntry.getLastAccessTime()) { + it.remove(); + trimmedByIdle++; + + } else if (maxSecsToLive > 0 && ttlExpire > cacheEntry.getCreateTime()) { + it.remove(); + trimmedByTTL++; + + } else if (trimMaxSize) { + activeList.add(cacheEntry); + } + } + + if (trimMaxSize) { + trimmedByLRU = activeList.size() - maxSize; + + if (trimmedByLRU > 0) { + // sort into last access time ascending + Collections.sort(activeList, comparator); + for (int i = maxSize; i < activeList.size(); i++) { + // remove if still in the cache + map.remove(activeList.get(i).getKey()); + } + } + } + + long exeTime = System.currentTimeMillis() - startTime; + + if (logger.isLoggable(Level.FINE)){ + logger.fine("Executed trim of cache " + name + " in ["+exeTime + +"]millis idle[" + trimmedByIdle + "] timeToLive[" + + trimmedByTTL + "] accessTime[" + + trimmedByLRU + "]"); + } + + } + + } + + /** + * Comparator for sorting by last access time. + */ + private static class CacheEntryComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + public int compare(CacheEntry o1, CacheEntry o2) { + + return o1.getLastAccessLong().compareTo(o2.getLastAccessLong()); + } + } + + /** + * Wraps the values to additionally hold createTime and lastAccessTime. + */ + public static class CacheEntry { + + private final Object key; + private final Object value; + private final long createTime; + private final AtomicInteger hitCount = new AtomicInteger(); + private Long lastAccessTime; + + public CacheEntry(Object key, Object value) { + this.key = key; + this.value = value; + this.createTime = System.currentTimeMillis(); + this.lastAccessTime = Long.valueOf(createTime); + } + + public Object getKey() { + return key; + } + + public Object getValue() { + // object assignment is atomic + hitCount.incrementAndGet(); + this.lastAccessTime = Long.valueOf(System.currentTimeMillis()); + return value; + } + + public long getCreateTime() { + return createTime; + } + + public long getLastAccessTime() { + return lastAccessTime.longValue(); + } + + public Long getLastAccessLong() { + return lastAccessTime; + } + + public int getHitCount(boolean reset) { + if (reset){ + return hitCount.getAndSet(0); + + } else { + return hitCount.get(); + } + } + public int getHitCount() { + return hitCount.get(); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheFactory.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheFactory.java new file mode 100644 index 000000000..db68b1ec8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheFactory.java @@ -0,0 +1,27 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.cache.ServerCache; +import com.avaje.ebean.cache.ServerCacheFactory; +import com.avaje.ebean.cache.ServerCacheOptions; + + +/** + * Default implementation of ServerCacheFactory. + */ +public class DefaultServerCacheFactory implements ServerCacheFactory { + + private EbeanServer ebeanServer; + + public void init(EbeanServer ebeanServer){ + this.ebeanServer = ebeanServer; + } + + public ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions) { + + ServerCache cache = new DefaultServerCache(cacheKey, cacheOptions); + cache.init(ebeanServer); + return cache; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java new file mode 100644 index 000000000..eac7ef78e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java @@ -0,0 +1,112 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.cache.ServerCache; +import com.avaje.ebean.cache.ServerCacheFactory; +import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.cache.ServerCacheOptions; +import com.avaje.ebeaninternal.api.SpiEbeanServer; + + +/** + * Manages the bean and query caches. + */ +public class DefaultServerCacheManager implements ServerCacheManager { + + private final DefaultCacheHolder beanCache; + + private final DefaultCacheHolder queryCache; + + private final DefaultCacheHolder naturalKeyCache; + + private final DefaultCacheHolder collectionIdsCache; + + private final ServerCacheFactory cacheFactory; + + private SpiEbeanServer ebeanServer; + + /** + * Create with a cache factory and default cache options. + */ + public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) { + this.cacheFactory = cacheFactory; + this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions, true); + this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false); + this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false); + this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false); + } + + public void init(EbeanServer server) { + cacheFactory.init(server); + this.ebeanServer = (SpiEbeanServer)server; + } + + public void setCaching(Class beanType, boolean useCache) { + ebeanServer.getBeanDescriptor(beanType).getCacheOptions().setUseCache(useCache); + } + + /** + * Clear both the bean cache and the query cache for a + * given bean type. + */ + public void clear(Class beanType) { + String beanName = beanType.getName(); + beanCache.clearCache(beanName); + naturalKeyCache.clearCache(beanName); + collectionIdsCache.clearCache(beanName); + queryCache.clearCache(beanName); + } + + + public void clearAll() { + beanCache.clearAll(); + queryCache.clearAll(); + naturalKeyCache.clearAll(); + collectionIdsCache.clearAll(); + } + + + public ServerCache getCollectionIdsCache(Class beanType, String propertyName) { + return collectionIdsCache.getCache(beanType.getName()+"."+propertyName); + } + + public boolean isCollectionIdsCaching(Class beanType) { + return collectionIdsCache.isCaching(beanType.getName()); + } + + public ServerCache getNaturalKeyCache(Class beanType) { + return naturalKeyCache.getCache(beanType.getName()); + } + + public boolean isNaturalKeyCaching(Class beanType) { + return naturalKeyCache.isCaching(beanType.getName()); + } + + /** + * Return the query cache for a given bean type. + */ + public ServerCache getQueryCache(Class beanType) { + return queryCache.getCache(beanType.getName()); + } + + /** + * Return the bean cache for a given bean type. + */ + public ServerCache getBeanCache(Class beanType) { + return beanCache.getCache(beanType.getName()); + } + + /** + * Return true if there is an active cache for the given bean type. + */ + public boolean isBeanCaching(Class beanType) { + return beanCache.isCaching(beanType.getName()); + } + + + public boolean isQueryCaching(Class beanType) { + return queryCache.isCaching(beanType.getName()); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/cache/package-info.java new file mode 100644 index 000000000..d142dd20d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/package-info.java @@ -0,0 +1,4 @@ +/** + * Default L2 server cache implementation. + */ +package com.avaje.ebeaninternal.server.cache; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessage.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessage.java new file mode 100644 index 000000000..0445adbd7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessage.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; + +/** + * Represents a relatively small independent message. + *

+ * In general terms we break up a potentially large object like + * RemoteTransactionEvent into many smaller BinaryMessages. This is so that if + * they don't all fit on a single Packet we can easily break them up and put + * them on multiple packets. + *

+ *

+ * Also note that for the Multicast approach a Packet will generally contain + * many messages each directed to different members of the cluster. So it would + * be common for many Ack, Resend and Control messages to all be contained in a + * single packet. + *

+ * + * @author rbygrave + * + */ +public class BinaryMessage { + + public static final int TYPE_MSGCONTROL = 0; + public static final int TYPE_BEANIUD = 1; + public static final int TYPE_TABLEIUD = 2; + public static final int TYPE_BEANDELTA = 3; + public static final int TYPE_BEANPATHUPDATE = 4; + public static final int TYPE_INDEX_INVALIDATE = 6; + public static final int TYPE_INDEX = 7; + public static final int TYPE_MSGACK = 8; + public static final int TYPE_MSGRESEND = 9; + + private final ByteArrayOutputStream buffer; + private final DataOutputStream os; + private byte[] bytes; + + /** + * Create with an estimated buffer size. + */ + public BinaryMessage(int bufSize) { + this.buffer = new ByteArrayOutputStream(bufSize); + this.os = new DataOutputStream(buffer); + } + + /** + * Return the DataOutputStream to write content to. + */ + public DataOutputStream getOs() { + return os; + } + + /** + * Return all the content as a byte array. + */ + public byte[] getByteArray() { + if (bytes == null) { + bytes = buffer.toByteArray(); + } + return bytes; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessageList.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessageList.java new file mode 100644 index 000000000..64d7d3168 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/BinaryMessageList.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.util.ArrayList; +import java.util.List; + +/** + * Holds a List of BinaryMessage's. + * + * @author rbygrave + */ +public class BinaryMessageList { + + ArrayList list = new ArrayList(); + + public void add(BinaryMessage msg) { + list.add(msg); + } + + public List getList() { + return list; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterBroadcast.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterBroadcast.java new file mode 100644 index 000000000..c184baf91 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterBroadcast.java @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; + + +/** + * Sends messages to the cluster members. + */ +public interface ClusterBroadcast { + + /** + * Inform the other cluster members that this instance has come online and + * start any listeners etc. + */ + public void startup(ClusterManager clusterManager); + + /** + * Inform the other cluster members that this instance is leaving and + * shutdown any listeners. + */ + public void shutdown(); + + /** + * Send a transaction event to all the members of the cluster. + */ + public void broadcast(RemoteTransactionEvent remoteTransEvent); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterManager.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterManager.java new file mode 100644 index 000000000..e595a0bda --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/ClusterManager.java @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.server.cluster.mcast.McastClusterManager; +import com.avaje.ebeaninternal.server.cluster.socket.SocketClusterBroadcast; +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; + +/** + * Manages the cluster service. + */ +public class ClusterManager { + + private static final Logger logger = Logger.getLogger(ClusterManager.class.getName()); + + private final ConcurrentHashMap serverMap = new ConcurrentHashMap(); + + private final Object monitor = new Object(); + + private final ClusterBroadcast broadcast; + + private boolean started; + + public ClusterManager() { + + String clusterType = GlobalProperties.get("ebean.cluster.type", null); + if (clusterType == null || clusterType.trim().length() == 0) { + // not clustering this instance + this.broadcast = null; + + } else { + + try { + if ("mcast".equalsIgnoreCase(clusterType)) { + this.broadcast = new McastClusterManager(); + + } else if ("socket".equalsIgnoreCase(clusterType)) { + this.broadcast = new SocketClusterBroadcast(); + + } else { + logger.info("Clustering using [" + clusterType + "]"); + this.broadcast = (ClusterBroadcast) ClassUtil.newInstance(clusterType); + } + + } catch (Exception e) { + String msg = "Error initialising ClusterManager type [" + clusterType + "]"; + logger.log(Level.SEVERE, msg, e); + throw new RuntimeException(e); + } + } + } + + public void registerServer(EbeanServer server) { + synchronized (monitor) { + if (!started) { + startup(); + } + serverMap.put(server.getName(), server); + } + } + + public EbeanServer getServer(String name) { + synchronized (monitor) { + return serverMap.get(name); + } + } + + private void startup() { + started = true; + if (broadcast != null) { + broadcast.startup(this); + } + } + + /** + * Return true if clustering is on. + */ + public boolean isClustering() { + return broadcast != null; + } + + /** + * Send the message headers and payload to every server in the cluster. + */ + public void broadcast(RemoteTransactionEvent remoteTransEvent) { + if (broadcast != null) { + broadcast.broadcast(remoteTransEvent); + } + } + + /** + * Shutdown the service and Deregister from the cluster. + */ + public void shutdown() { + if (broadcast != null) { + logger.info("ClusterManager shutdown "); + broadcast.shutdown(); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/DataHolder.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/DataHolder.java new file mode 100644 index 000000000..bfb0da2fe --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/DataHolder.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.io.Serializable; + +/** + * Simple holder of binary data. + * Used to use Packet based serialisation of RemoteTransactionEvent + * with simple Java Serialisation of the DataHolder. + */ +public class DataHolder implements Serializable { + + private static final long serialVersionUID = 9090748723571322192L; + + private final byte[] data; + + public DataHolder(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/Packet.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/Packet.java new file mode 100644 index 000000000..cc33392e1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/Packet.java @@ -0,0 +1,212 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Represents the contents sent as a single DatagramPacket. + *

+ * The contents is typically multiple messages (ACK,PING etc) or all or part of + * a RemoteTransactionEvent. + *

+ *

+ * Due to the hard limit on the size of UDP packets a RemoteTransactionEvent + * with lots of information could be broken up into multiple packets. + *

+ * + * @author rbygrave + */ +public class Packet { + + /** + * A Packet that holds protocol messages like ACK, PING etc. + */ + public static final short TYPE_MESSAGES = 1; + + /** + * A Packet that holds TransactionEvent information such as Bean + * and or Table IUD information. + */ + public static final short TYPE_TRANSEVENT = 2; + + /** + * The type of Packet. + */ + protected short packetType; + + /** + * The PacketId. + */ + protected long packetId; + + /** + * The timestamp the Packet was created. + */ + protected long timestamp; + + /** + * The EbeanServer name this relates to if relevant. + */ + protected String serverName; + + protected ByteArrayOutputStream buffer; + protected DataOutputStream dataOut; + protected byte[] bytes; + + /** + * The number of messages in this Packet. + */ + private int messageCount; + + /** + * The number of times this Packet was resent. + */ + private int resendCount; + + /** + * Create a Packet for writing messages to. + */ + public static Packet forWrite(short packetType, long packetId, long timestamp, String serverName) throws IOException { + return new Packet(true, packetType, packetId, timestamp, serverName); + } + + /** + * Create a Packet just reading the Header information. + */ + public static Packet readHeader(DataInput dataInput) throws IOException { + + short packetType = dataInput.readShort(); + long packetId = dataInput.readLong(); + long timestamp = dataInput.readLong(); + String serverName = dataInput.readUTF(); + + return new Packet(false, packetType, packetId, timestamp, serverName); + } + + protected Packet(boolean write, short packetType, long packetId, long timestamp, String serverName) throws IOException{ + this.packetType = packetType; + this.packetId = packetId; + this.timestamp = timestamp; + this.serverName = serverName; + if (write){ + this.buffer = new ByteArrayOutputStream(); + this.dataOut = new DataOutputStream(buffer); + writeHeader(); + } else { + this.buffer = null; + this.dataOut = null; + } + } + + private void writeHeader() throws IOException { + dataOut.writeShort(packetType); + dataOut.writeLong(packetId); + dataOut.writeLong(timestamp); + dataOut.writeUTF(serverName); + } + + public int incrementResendCount() { + return resendCount++; + } + + public short getPacketType() { + return packetType; + } + + public long getPacketId() { + return packetId; + } + + public long getTimestamp() { + return timestamp; + } + + public String getServerName() { + return serverName; + } + + public void writeEof() throws IOException { + dataOut.writeBoolean(false); + } + + public void read(DataInput dataInput) throws IOException { + boolean more = dataInput.readBoolean(); + while (more){ + int msgType = dataInput.readInt(); + readMessage(dataInput, msgType); + // see if there is more information + more = dataInput.readBoolean(); + } + } + + /** + * Overridden by more specific Packet implementations to read the messages. + */ + protected void readMessage(DataInput dataInput, int msgType) throws IOException { + + } + + /** + * Write a binary message to this packet returning true if there was + * enough room to do so. Return false if the message was too large for + * the remaining space left - in this case another Packet should be + * created to put that message into. + */ + public boolean writeBinaryMessage(BinaryMessage msg, int maxPacketSize) throws IOException { + + byte[] bytes = msg.getByteArray(); + + if (messageCount > 0 && (bytes.length + buffer.size() > maxPacketSize)){ + // we are actually going to ignore the maxPacketSize iff we have one + // large message. + + // false = no more messages + dataOut.writeBoolean(false); + return false; + } + ++messageCount; + // true = another message follows + dataOut.writeBoolean(true); + dataOut.write(bytes); + return true; + } + + public int getSize() { + return getBytes().length; + } + + /** + * Return the Packet as raw bytes. + */ + public byte[] getBytes() { + if (bytes == null){ + bytes = buffer.toByteArray(); + buffer = null; + dataOut = null; + } + return bytes; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketMessages.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketMessages.java new file mode 100644 index 000000000..53a182133 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketMessages.java @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.io.DataInput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.cluster.mcast.Message; +import com.avaje.ebeaninternal.server.cluster.mcast.MessageAck; +import com.avaje.ebeaninternal.server.cluster.mcast.MessageControl; +import com.avaje.ebeaninternal.server.cluster.mcast.MessageResend; + +/** + * A Packet that contains Ack, Resend and Control messages. + * + * @author rbygrave + */ +public class PacketMessages extends Packet { + + private final ArrayList messages; + + public static PacketMessages forWrite(long packetId, long timestamp, String serverName) throws IOException { + return new PacketMessages(true, packetId, timestamp, serverName); + } + + public static PacketMessages forRead(Packet header) throws IOException { + return new PacketMessages(header); + } + + private PacketMessages(boolean write, long packetId, long timestamp, String serverName) throws IOException { + super(write, TYPE_MESSAGES, packetId, timestamp, serverName); + this.messages = null; + } + + private PacketMessages(Packet header) throws IOException { + super(false, TYPE_MESSAGES, header.packetId, header.timestamp, header.serverName); + this.messages = new ArrayList(); + } + + /** + * Return the messages contained in this Packet. + */ + public List getMessages() { + return messages; + } + + /** + * Read the messages (Ack, Resend or Control) contained in this packet. + */ + protected void readMessage(DataInput dataInput, int msgType) throws IOException { + + switch (msgType) { + case BinaryMessage.TYPE_MSGCONTROL: + messages.add(MessageControl.readBinaryMessage(dataInput)); + break; + + case BinaryMessage.TYPE_MSGACK: + messages.add(MessageAck.readBinaryMessage(dataInput)); + break; + + case BinaryMessage.TYPE_MSGRESEND: + messages.add(MessageResend.readBinaryMessage(dataInput)); + break; + + default: + throw new RuntimeException("Invalid Transaction msgType "+msgType); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketTransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketTransactionEvent.java new file mode 100644 index 000000000..a746d28d8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketTransactionEvent.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.io.DataInput; +import java.io.IOException; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; +import com.avaje.ebeaninternal.server.transaction.BeanDelta; +import com.avaje.ebeaninternal.server.transaction.BeanPersistIds; +import com.avaje.ebeaninternal.server.transaction.IndexEvent; +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; + +/** + * A Packet holding TransactionEvent data. + *

+ * Due to the hard limit for UDP packet sizes a RemoteTransactionEvent + * is actually broken up into smaller messages. + *

+ * @author rbygrave + */ +public class PacketTransactionEvent extends Packet { + + private final SpiEbeanServer server; + + private final RemoteTransactionEvent event; + + public static PacketTransactionEvent forWrite(long packetId, long timestamp, String serverName) throws IOException { + return new PacketTransactionEvent(true, packetId, timestamp, serverName); + } + + private PacketTransactionEvent(boolean write, long packetId, long timestamp, String serverName) throws IOException { + super(write, TYPE_TRANSEVENT, packetId, timestamp, serverName); + this.server = null; + this.event = null; + } + + private PacketTransactionEvent(Packet header, SpiEbeanServer server) throws IOException { + super(false, TYPE_TRANSEVENT, header.packetId, header.timestamp, header.serverName); + this.server = server; + this.event = new RemoteTransactionEvent(server); + } + + public static PacketTransactionEvent forRead(Packet header, SpiEbeanServer server) throws IOException { + return new PacketTransactionEvent(header, server); + } + + public RemoteTransactionEvent getEvent() { + return event; + } + + protected void readMessage(DataInput dataInput, int msgType) throws IOException { + + switch (msgType) { + case BinaryMessage.TYPE_BEANIUD: + event.addBeanPersistIds(BeanPersistIds.readBinaryMessage(server, dataInput)); + break; + + case BinaryMessage.TYPE_TABLEIUD: + event.addTableIUD(TableIUD.readBinaryMessage(dataInput)); + break; + + case BinaryMessage.TYPE_BEANDELTA: + event.addBeanDelta(BeanDelta.readBinaryMessage(server, dataInput)); + break; + + case BinaryMessage.TYPE_INDEX: + event.addIndexEvent(IndexEvent.readBinaryMessage(dataInput)); + break; + + default: + throw new RuntimeException("Invalid Transaction msgType "+msgType); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketWriter.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketWriter.java new file mode 100644 index 000000000..a868fab63 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/PacketWriter.java @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.cluster.mcast.Message; +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; + +/** + * Creates Packets for either RemoteTransactionEvents or Messages (Ping, ACK, + * Join, Leave etc). + * + * @author rbygrave + */ +public class PacketWriter { + + private final PacketIdGenerator idGenerator; + private final PacketBuilder messagesPacketBuilder; + private final PacketBuilder transEventPacketBuilder; + + /** + * Create a PacketWriter with an expected max packet size. + *

+ * In theory we would prefer to create packets up to the MTU size which for + * Ethernet will likely be 1500. Note that the maxPacketSize is ignored for + * large single messages. + *

+ */ + public PacketWriter(int maxPacketSize) { + this.idGenerator = new PacketIdGenerator(); + this.messagesPacketBuilder = new PacketBuilder(maxPacketSize, idGenerator, new MessagesPacketFactory()); + this.transEventPacketBuilder = new PacketBuilder(maxPacketSize, idGenerator, new TransPacketFactory()); + } + + /** + * Return the currentPacketId. + */ + public long currentPacketId() { + return idGenerator.currentPacketId(); + } + + /** + * Create Packets for a given list of messages. + *

+ * Typically this creates a single Packet but there is a hard limit for UDP + * packet sizes. + *

+ */ + public List write(boolean requiresAck, List messages) throws IOException { + + BinaryMessageList binaryMsgList = new BinaryMessageList(); + for (int i = 0; i < messages.size(); i++) { + Message message = messages.get(i); + message.writeBinaryMessage(binaryMsgList); + } + return messagesPacketBuilder.write(requiresAck, binaryMsgList, ""); + } + + /** + * Create Packets for a given RemoteTransactionEvent. + *

+ * Typically this creates a single Packet but there is a hard limit for UDP + * packet sizes. + *

+ */ + public List write(RemoteTransactionEvent transEvent) throws IOException { + + BinaryMessageList messageList = new BinaryMessageList(); + + // split into reasonably small independent messages + transEvent.writeBinaryMessage(messageList); + + return transEventPacketBuilder.write(true, messageList, transEvent.getServerName()); + } + + /** + * Reuse the same packetIdCounter for building Packets for both Message and + * RemoteTransactionEvent + */ + private static class PacketIdGenerator { + + long packetIdCounter; + + public long nextPacketId() { + return ++packetIdCounter; + } + + public long currentPacketId() { + return packetIdCounter; + } + + } + + interface PacketFactory { + + public Packet createPacket(long packetId, long timestamp, String serverName) throws IOException; + } + + private static class TransPacketFactory implements PacketFactory { + + public Packet createPacket(long packetId, long timestamp, String serverName) throws IOException { + return PacketTransactionEvent.forWrite(packetId, timestamp, serverName); + } + } + + private static class MessagesPacketFactory implements PacketFactory { + + public Packet createPacket(long packetId, long timestamp, String serverName) throws IOException { + return PacketMessages.forWrite(packetId, timestamp, serverName); + } + } + + /** + * Helper class for building Packets from messages or + * RemoteTransactionEvents. + */ + private static class PacketBuilder { + + private final PacketIdGenerator idGenerator; + private final PacketFactory packetFactory; + private final int maxPacketSize; + + private PacketBuilder(int maxPacketSize, PacketIdGenerator idGenerator, PacketFactory packetFactory) { + this.maxPacketSize = maxPacketSize; + this.idGenerator = idGenerator; + this.packetFactory = packetFactory; + } + + private List write(boolean requiresAck, BinaryMessageList messageList, String serverName) + throws IOException { + + List list = messageList.getList(); + + ArrayList packets = new ArrayList(1); + + long timestamp = System.currentTimeMillis(); + + long packetId = requiresAck ? idGenerator.nextPacketId() : 0; + Packet p = packetFactory.createPacket(packetId, timestamp, serverName); + + packets.add(p); + + for (int i = 0; i < list.size(); i++) { + BinaryMessage binMsg = list.get(i); + if (!p.writeBinaryMessage(binMsg, maxPacketSize)) { + // didn't fit into the package so put into another packet + packetId = requiresAck ? idGenerator.nextPacketId() : 0; + p = packetFactory.createPacket(packetId, timestamp, serverName); + packets.add(p); + p.writeBinaryMessage(binMsg, maxPacketSize); + } + } + p.writeEof(); + + return packets; + + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/SerialiseTransactionHelper.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/SerialiseTransactionHelper.java new file mode 100644 index 000000000..9e9b14130 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/SerialiseTransactionHelper.java @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.List; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; + +/** + * Mechanism to convert RemoteTransactionEvent to/from byte[] content. + */ +public abstract class SerialiseTransactionHelper { + + private final PacketWriter packetWriter; + + public SerialiseTransactionHelper() { + packetWriter = new PacketWriter(Integer.MAX_VALUE); + } + + public abstract SpiEbeanServer getEbeanServer(String serverName); + + /** + * Convert the RemoteTransactionEvent to byte[] content. + */ + public DataHolder createDataHolder(RemoteTransactionEvent transEvent) throws IOException { + + List packetList = packetWriter.write(transEvent); + if (packetList.size() != 1) { + throw new RuntimeException("Always expecting 1 Packet but got " + packetList.size()); + } + byte[] data = packetList.get(0).getBytes(); + return new DataHolder(data); + } + + /** + * Convert the byte[] content to RemoteTransactionEvent. + */ + public RemoteTransactionEvent read(DataHolder dataHolder) throws IOException { + + ByteArrayInputStream bi = new ByteArrayInputStream(dataHolder.getData()); + DataInputStream dataInput = new DataInputStream(bi); + + Packet header = Packet.readHeader(dataInput); + + SpiEbeanServer server = getEbeanServer(header.getServerName()); + + PacketTransactionEvent tranEventPacket = PacketTransactionEvent.forRead(header, server); + tranEventPacket.read(dataInput); + + return tranEventPacket.getEvent(); + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/AckResendMessages.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/AckResendMessages.java new file mode 100644 index 000000000..fa9ef1b84 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/AckResendMessages.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.util.ArrayList; +import java.util.List; + +/** + * Holds a list of ACK and RESEND messages that should be sent out. + * + * @author rbygrave + */ +public class AckResendMessages { + + ArrayList messages = new ArrayList(); + + public String toString() { + return messages.toString(); + } + + public int size() { + return messages.size(); + } + + /** + * Add a ACK message to send. + */ + public void add(MessageAck ack){ + messages.add(ack); + } + + /** + * Add a RESEND message to send. + */ + public void add(MessageResend resend){ + messages.add(resend); + } + + /** + * Return all the messages to be sent out. + */ + public List getMessages() { + return messages; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsLastAck.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsLastAck.java new file mode 100644 index 000000000..2c47538cf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsLastAck.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.util.HashMap; +import java.util.List; + +/** + * For this node this holds the ACK gotAllPoint for each member in the cluster. + *

+ * As we receive messages from other members of the cluster periodically we need + * to send them ACK messages to say we got all the packets up to the gotAllPoint. + *

+ * Thread Safety note: Object only used by McastClusterBroadcast Manager thread. + * So Single Threaded access. + * + * @author rbygrave + */ +public class IncomingPacketsLastAck { + + private HashMap lastAckMap = new HashMap(); + + public String toString() { + return lastAckMap.values().toString(); + } + + /** + * Remove a member of the cluster who has left. + */ + public void remove(String memberHostPort) { + lastAckMap.remove(memberHostPort); + } + + /** + * Get the last Ack point for a given member of the cluster. + */ + public MessageAck getLastAck(String memberHostPort) { + return lastAckMap.get(memberHostPort); + } + + /** + * For the ACK messages in AckResendMessages update the + * last Ack packetId. + */ + public void updateLastAck(AckResendMessages ackResendMessages) { + List messages = ackResendMessages.getMessages(); + for (int i = 0; i < messages.size(); i++) { + Message msg = messages.get(i); + if (msg instanceof MessageAck){ + MessageAck lastAck = (MessageAck)msg; + lastAckMap.put(lastAck.getToHostPort(), lastAck); + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsProcessed.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsProcessed.java new file mode 100644 index 000000000..6d1cad167 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/IncomingPacketsProcessed.java @@ -0,0 +1,292 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +/** + * For Incoming Packets remembers the packets we have received and processed. + *

+ * This determines the gotAllPoint per cluster member and identifies missing + * packets (gap between gotAllPoint and gotMaxPoint). + *

+ *

+ * This information is used by the managerThread so send ACK's for messages we + * have received and RESEND messages to fill the missing packets we have + * detected. + *

+ * + * @author rbygrave + * + */ +public class IncomingPacketsProcessed { + + private final ConcurrentHashMap mapByMember = new ConcurrentHashMap(); + + private final int maxResendIncoming; + + public IncomingPacketsProcessed(int maxResendIncoming) { + this.maxResendIncoming = maxResendIncoming; + } + + public void removeMember(String memberKey) { + mapByMember.remove(memberKey); + } + + /** + * Return true if we should process this packet. Return false if we have + * already processed the packet. + */ + public boolean isProcessPacket(String memberKey, long packetId) { + + GotAllPoint memberPackets = getMemberPackets(memberKey); + return memberPackets.processPacket(packetId); + } + + /** + * Build the list of ACK and RESEND messages that we should send out + * to the other members of the cluster. + */ + public AckResendMessages getAckResendMessages(IncomingPacketsLastAck lastAck) { + + // Called by the McastClusterBroadcast manager thread + + AckResendMessages response = new AckResendMessages(); + + for (GotAllPoint member : mapByMember.values()) { + + MessageAck lastAckMessage = lastAck.getLastAck(member.getMemberKey()); + + member.addAckResendMessages(response, lastAckMessage); + } + + return response; + } + + private GotAllPoint getMemberPackets(String memberKey) { + + // This method is only called single threaded + // by the listener thread so I'm happy that this + // put into mapByMember is ok. + GotAllPoint memberGotAllPoint = mapByMember.get(memberKey); + if (memberGotAllPoint == null) { + memberGotAllPoint = new GotAllPoint(memberKey, maxResendIncoming); + mapByMember.put(memberKey, memberGotAllPoint); + } + return memberGotAllPoint; + } + + /** + * Keeps track of packets received from a particular member of the cluster. + *

+ * It notes the packetIds of the packets received and uses those to maintain + * the 'gotAllPoint'. The 'gotAllPoint' is the packetId which we know we + * received all the previous packets. + *

+ */ + public static class GotAllPoint { + + private static final Logger logger = Logger.getLogger(GotAllPoint.class.getName()); + + private final String memberKey; + private final int maxResendIncoming; + + private long gotAllPoint; + + private long gotMaxPoint; + + /** + * Packets received out of order. + */ + private ArrayList outOfOrderList = new ArrayList(); + + private HashMap resendCountMap = new HashMap(); + + public GotAllPoint(String memberKey, int maxResendIncoming) { + this.memberKey = memberKey; + this.maxResendIncoming = maxResendIncoming; + } + + /** + * Add ACK and RESEND messages if required. + */ + public void addAckResendMessages(AckResendMessages response, MessageAck lastAckMessage) { + + synchronized (this) { + if (lastAckMessage != null && lastAckMessage.getGotAllPacketId() >= gotAllPoint) { + // nothing has changed + } else { + // ACK that we have got every packet up to gotAllPoint + response.add(new MessageAck(memberKey, gotAllPoint)); + } + + if (getMissingPacketCount() > 0) { + // Ask for these Packets to be RESENT + List missingPackets = getMissingPackets(); + response.add(new MessageResend(memberKey, missingPackets)); + } + } + } + + public String getMemberKey() { + return memberKey; + } + + public long getGotAllPoint() { + synchronized (this) { + return gotAllPoint; + } + } + + public long getGotMaxPoint() { + synchronized (this) { + return gotMaxPoint; + } + } + + private int getMissingPacketCount() { + if (gotMaxPoint <= gotAllPoint) { + if (!resendCountMap.isEmpty()) { + resendCountMap.clear(); + } + return 0; + } + return (int) (gotMaxPoint - gotAllPoint) - outOfOrderList.size(); + } + + public List getMissingPackets() { + + synchronized (this) { + ArrayList missingList = new ArrayList(); + + // this is not particularly efficient but expecting + // the outOfOrderList to be relatively small + + boolean lostPacket = false; + + for (long i = gotAllPoint + 1; i < gotMaxPoint; i++) { + Long packetId = Long.valueOf(i); + if (!outOfOrderList.contains(packetId)) { + if (incrementResendCount(packetId)) { + // request this packet be resent + missingList.add(packetId); + } else { + lostPacket = true; + } + } + } + + if (lostPacket){ + checkOutOfOrderList(); + } + + return missingList; + } + } + + /** + * Return true if this packet has not yet exceeded the maxResendCount. + */ + private boolean incrementResendCount(Long packetId){ + Integer resendCount = resendCountMap.get(packetId); + if (resendCount != null){ + int i = resendCount.intValue() + 1; + if (i > maxResendIncoming){ + // we are going to give up trying to get this packet now + logger.warning("Exceeded maxResendIncoming["+maxResendIncoming+"] for packet["+packetId+"]. Giving up on requesting it."); + resendCountMap.remove(packetId); + outOfOrderList.add(packetId); + return false; + } + resendCount = Integer.valueOf(i); + resendCountMap.put(packetId, resendCount); + } else { + resendCountMap.put(packetId, ONE); + } + return true; + } + + private static final Integer ONE = Integer.valueOf(1); + + public boolean processPacket(long packetId) { + synchronized (this) { + + if (gotAllPoint == 0) { + gotAllPoint = packetId; + return true; + } + if (packetId <= gotAllPoint) { + // already processed this packet + return false; + } + + if (!resendCountMap.isEmpty()){ + resendCountMap.remove(Long.valueOf(packetId)); + } + + if (packetId == gotAllPoint + 1) { + gotAllPoint = packetId; + } else { + if (packetId > gotMaxPoint) { + gotMaxPoint = packetId; + } + outOfOrderList.add(Long.valueOf(packetId)); + } + checkOutOfOrderList(); + return true; + } + } + + private void checkOutOfOrderList() { + + if (outOfOrderList.size() == 0) { + return; + } + + boolean continueCheck; + do { + continueCheck = false; + long nextPoint = gotAllPoint + 1; + + Iterator it = outOfOrderList.iterator(); + while (it.hasNext()) { + Long id = it.next(); + if (id.longValue() == nextPoint) { + // we found the next one in the outOfOrderList + it.remove(); + gotAllPoint = nextPoint; + continueCheck = true; + break; + } + } + } while (continueCheck); + + } + + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastClusterManager.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastClusterManager.java new file mode 100644 index 000000000..4fef7370b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastClusterManager.java @@ -0,0 +1,614 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.cluster.ClusterBroadcast; +import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.cluster.Packet; +import com.avaje.ebeaninternal.server.cluster.PacketWriter; +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; + +/** + * Overall Manager of the Multicast Cluster communication for this instance. + *

+ * McastListener, McastSender and McastPacketControl are the main helpers to + * this object. + *

+ *

+ * This Manager (thread) periodically processes the ACK, Re-send and Control + * messages. The McastListener is handling all the incoming packets and informs + * this manager when interesting packets need to be processed by the Manager. + *

+ *

+ * Other threads call {@link #broadcast(RemoteTransactionEvent)} to send + * transaction even information. + *

+ * + * @author rbygrave + */ +public class McastClusterManager implements ClusterBroadcast, Runnable { + + private static final Logger logger = Logger.getLogger(McastClusterManager.class.getName()); + + private ClusterManager clusterManager; + + private final Thread managerThread; + + /** + * Helps co-ordinate packet information (Acks, Missing Packets etc). + */ + private final McastPacketControl packageControl; + + /** + * Listeners for incoming packets. + */ + private final McastListener listener; + + /** + * Sends packets out to the cluster. + */ + private final McastSender localSender; + + /** + * The localSenderHostPort is used to identify this instance in the cluster. + */ + private final String localSenderHostPort; + + /** + * Creates the Packets (byte[]) from Messages and RemoteTransactionEvent. + */ + private final PacketWriter packetWriter; + + /** + * List of Re-send messages that the managerThread needs to process. + */ + private final ArrayList resendMessages = new ArrayList(); + + /** + * List of Control messages (Ping,PingResponse,Join,Leave) that the managerThread needs to process. + */ + private final ArrayList controlMessages = new ArrayList(); + + /** + * Cache of outgoing messages that have not been ACK'ed by the other cluster members yet. + */ + private final OutgoingPacketsCache outgoingPacketsCache = new OutgoingPacketsCache(); + + /** + * The last ACK we sent out to other members of the cluster. + */ + private final IncomingPacketsLastAck incomingPacketsLastAck = new IncomingPacketsLastAck(); + + /** + * A limit of the number of times we will try to send out a given packet. + * Once this is exceeded we will just drop that packet. Hopefully this does + * not happen but we don't want to keep trying forever producing network + * traffic. + */ + private final int maxResendOutgoing; + + /** + * Instead of ACK'ing immediately we periodically wake up and in a single + * packet (typically) ACK all members of the cluster everything we got since + * the last sleep time. More frequent ACK's means less memory consumption as + * Packets are cleared from the outgoingPacketsCache quicker at the cost of + * sending more packets. + */ + private long managerSleepMillis; + + /** + * When true then packets are still sent out even when the cluster has no other online members. + */ + private boolean sendWithNoMembers; + + /** + * The current minAcked packetId processed by the managerThread. + * All packets before this have been ACK'ed by everyone in the cluster. + */ + private long minAcked; + + /** + * The min packetId that has been ACKed by all the members of the cluster according + * to the McastListener. This will increase as the Listener receives ACK's and means + * we can trim out Packets from the sent cache. + */ + private long minAckedFromListener; + + /** + * Start the groupSize at -1 so we have to wait until the Listener times out or gets + * a control messages (Ping, PingResponse, Join, Leave etc) before we know how many + * members of the group the listener knows about. + *

+ * Generally speaking we only care if the groupSize == 0 meaning there are no other + * members of the cluster that are online. In this case we can potentially not send + * the packets out (depending on sendWithNoMembers) and not cache them (for re-sending + * if they where not ACK'ed). + *

+ */ + private int currentGroupSize = -1; + + /** + * The last time a packet was sent from this node. + */ + private long lastSendTime; + + /** + * The max time we go without sending any packets. + */ + private int lastSendTimeFreqMillis; + + /** + * The last time the cluster status was logged. + */ + private long lastStatusTime = System.currentTimeMillis(); + + /** + * The max time we go before logging the cluster status. + */ + private int lastStatusTimeFreqMillis; + + + private long totalTxnEventsSent; + private long totalTxnEventsReceived; + + private long totalPacketsSent; + private long totalBytesSent; + + private long totalPacketsResent; + private long totalBytesResent; + + private long totalPacketsReceived; + private long totalBytesReceived; + + + public McastClusterManager() { + + this.managerSleepMillis = GlobalProperties.getInt("ebean.cluster.mcast.managerSleepMillis", 80); + this.lastSendTimeFreqMillis = 1000*GlobalProperties.getInt("ebean.cluster.mcast.pingFrequencySecs", 300);//5mins + this.lastStatusTimeFreqMillis = 1000*GlobalProperties.getInt("ebean.cluster.mcast.statusFrequencySecs", 600);//10mins + + // the maximum number of times we will try to re-send a given packet before giving up sending + this.maxResendOutgoing = GlobalProperties.getInt("ebean.cluster.mcast.maxResendOutgoing", 200); + // the maximum number of times we will ask for a packet to be resent to us before giving up asking + int maxResendIncoming = GlobalProperties.getInt("ebean.cluster.mcast.maxResendIncoming", 50); + + + int port = GlobalProperties.getInt("ebean.cluster.mcast.listen.port", 0); + String addr = GlobalProperties.get("ebean.cluster.mcast.listen.address", null); + + int sendPort = GlobalProperties.getInt("ebean.cluster.mcast.send.port", 0); + String sendAddr = GlobalProperties.get("ebean.cluster.mcast.send.address", null); + + // Sender options + // Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507 + int maxSendPacketSize = GlobalProperties.getInt("ebean.cluster.mcast.send.maxPacketSize", 1500); + // Whether to send packets even when there are no other members online + this.sendWithNoMembers = GlobalProperties.getBoolean("ebean.cluster.mcast.send.sendWithNoMembers", true); + + // Listener options + // When multiple instances are on same box you need to broadcast back locally + boolean disableLoopback = GlobalProperties.getBoolean("ebean.cluster.mcast.listen.disableLoopback", false); + int ttl = GlobalProperties.getInt("ebean.cluster.mcast.listen.ttl", -1); + int timeout = GlobalProperties.getInt("ebean.cluster.mcast.listen.timeout", 1000); + int bufferSize = GlobalProperties.getInt("ebean.cluster.mcast.listen.bufferSize", 65500); + // For multihomed environment the address the listener should bind to + String mcastAddr = GlobalProperties.get("ebean.cluster.mcast.listen.mcastAddress", null); + + InetAddress mcastAddress = null; + if (mcastAddr != null) { + try { + mcastAddress = InetAddress.getByName(mcastAddr); + } catch (UnknownHostException e) { + String msg = "Error getting Multicast InetAddress for " + mcastAddr; + throw new RuntimeException(msg, e); + } + } + + if (port == 0 || addr == null) { + String msg = "One of these Multicast settings has not been set. " + "ebean.cluster.mcast.listen.port=" + + port + ", ebean.cluster.mcast.listen.address=" + addr; + + throw new IllegalArgumentException(msg); + } + + this.managerThread = new Thread(this, "EbeanClusterMcastManager"); + + this.packetWriter = new PacketWriter(maxSendPacketSize); + this.localSender = new McastSender(port, addr, sendPort, sendAddr); + this.localSenderHostPort = localSender.getSenderHostPort(); + + this.packageControl = new McastPacketControl(this, localSenderHostPort, maxResendIncoming); + + this.listener = new McastListener(this, packageControl, port, addr, bufferSize, timeout, localSenderHostPort, + disableLoopback, ttl, mcastAddress); + } + + + /** + * The McastListener tells us there are no other members of the cluster that + * are currently online. + */ + protected void fromListenerTimeoutNoMembers() { + synchronized (managerThread) { + this.currentGroupSize = 0; + } + } + + /** + * McastListener calls this method to get the manager to process messages. + * + * @param newMinAcked + * the minAcked packetId according to the listener + * @param msgControl + * a control message to process + * @param msgResend + * a Please re-send message to process + * @param groupSize + * the number of other online members + */ + protected void fromListener(long newMinAcked, MessageControl msgControl, MessageResend msgResend, + int groupSize, long totalPacketsReceived, long totalBytesReceived, long totalTxnEventsReceived) { + + synchronized (managerThread) { + if (newMinAcked > minAckedFromListener){ + minAckedFromListener = newMinAcked; + } + if (msgControl != null){ + controlMessages.add(msgControl); + } + if (msgResend != null){ + resendMessages.add(msgResend); + } + // mostly interested when groupSize hits 0 (we are the only instance online). + this.currentGroupSize = groupSize; + + // and some stats so we know how busy the listener has been + this.totalPacketsReceived = totalPacketsReceived; + this.totalBytesReceived = totalBytesReceived; + this.totalTxnEventsReceived = totalTxnEventsReceived; + } + } + + /** + * Get the overall status and activity of this cluster node. + */ + public McastStatus getStatus(boolean reset) { + synchronized (managerThread) { + + long currentPacketId = packetWriter.currentPacketId(); + String lastAcks = incomingPacketsLastAck.toString(); + + return new McastStatus(currentGroupSize, outgoingPacketsCache.size(), currentPacketId, minAcked, lastAcks, + totalTxnEventsSent, totalTxnEventsReceived, totalPacketsSent, totalPacketsResent, totalPacketsReceived, + totalBytesSent, totalBytesResent, totalBytesReceived); + + } + } + + /** + * Periodically send out Ack, Re-send and Control messages. + */ + public void run() { + while (true) { + try { + // sleep for a little bit as we ACK packets periodically + // rather than immediately. We will typically ACK many + // messages from all cluster members in a single Packet + Thread.sleep(managerSleepMillis); + + synchronized (managerThread) { + + handleControlMessages(); + + handleResendMessages(); + + if (currentGroupSize == 0){ + // no members online so trim the entire outgoing packets cache + int trimmedCount = outgoingPacketsCache.trimAll(); + if (trimmedCount > 0){ + logger.fine("Cluster has no other members. Trimmed "+trimmedCount); + } + + } else if (minAckedFromListener > minAcked){ + // ACKs have come back so trim send packets cache + outgoingPacketsCache.trimAcknowledgedMessages(minAckedFromListener); + minAcked = minAckedFromListener; + } + + // Get list of all the ACK messages required to sent since the last time. + // This is effectively one ACK message per member of the cluster. The ACK + // message covers all the packets received from the member up to + // the gotAllPoint. + // Also get any RESEND messages asking for packets that we have not + // received between the gotAllPoint and the gotMaxPoint. + AckResendMessages ackResendMessages = packageControl.getAckResendMessages(incomingPacketsLastAck); + + if (ackResendMessages.size() > 0){ + // send the ACK and RESEND messages for all members of the + // cluster typically in a single Packet + if (sendMessages(false, ackResendMessages.getMessages())) { + // update the last Ack position + incomingPacketsLastAck.updateLastAck(ackResendMessages); + } + } + + if (lastSendTime < System.currentTimeMillis() - lastSendTimeFreqMillis){ + // been quite for too long - send a Ping out + sendPing(); + } + + if (lastStatusTimeFreqMillis > 0){ + if (lastStatusTime < System.currentTimeMillis() - lastStatusTimeFreqMillis){ + McastStatus status = getStatus(false); + logger.info("Cluster Status: "+status.getSummary()); + lastStatusTime = System.currentTimeMillis(); + } + } + + } + } catch (Exception e){ + String msg = "Error with Cluster Mcast Manager thread"; + logger.log(Level.SEVERE, msg, e); + } + } + } + + /** + * We have been asked to Re-send some packets. + */ + private void handleResendMessages() { + + if (resendMessages.size() > 0){ + + TreeSet s = new TreeSet(); + for (int i = 0; i < resendMessages.size(); i++) { + MessageResend resendMsg = resendMessages.get(i); + s.addAll(resendMsg.getResendPacketIds()); + } + + totalPacketsResent += s.size(); + + Iterator it = s.iterator(); + while (it.hasNext()) { + Long resendPacketId = it.next(); + Packet packet = outgoingPacketsCache.getPacket(resendPacketId); + if (packet == null){ + String msg = "Cluster unable to resend packet["+resendPacketId+"] as it is no longer in the outgoingPacketsCache"; + logger.log(Level.SEVERE, msg); + } else { + int resendCount = packet.incrementResendCount(); + if (resendCount <= maxResendOutgoing) { + resendPacket(packet); + } else { + String msg = "Cluster maxResendOutgoing ["+maxResendOutgoing+"] hit for packet "+resendPacketId + +". We will not try to send it anymore, removing it from the outgoingPacketsCache."; + logger.log(Level.SEVERE, msg); + outgoingPacketsCache.remove(packet); + } + } + } + } + } + + /** + * Re-send a packet that a member didn't seem to receive. + */ + private void resendPacket(Packet packet) { + try { + ++totalPacketsResent; + totalBytesResent += localSender.sendPacket(packet); + } catch (IOException e) { + String msg = "Error trying to resend packet "+packet.getPacketId(); + logger.log(Level.SEVERE, msg, e); + } + } + + /** + * Handle Control messages (Join, Leave, Ping). + */ + private void handleControlMessages() { + + boolean pingReponse = false; + boolean joinReponse = false; + + for (int i = 0; i < controlMessages.size(); i++) { + MessageControl message = controlMessages.get(i); + + short type = message.getControlType(); + switch (type) { + case MessageControl.TYPE_JOIN: + // a new member online, send back a Join Response + logger.info("Cluster member Joined ["+message.getFromHostPort()+"]"); + joinReponse = true; + break; + + case MessageControl.TYPE_JOINRESPONSE: + logger.info("Cluster member Online ["+message.getFromHostPort()+"]"); + // do nothing + break; + + case MessageControl.TYPE_PING: + pingReponse = true; + break; + + case MessageControl.TYPE_PINGRESPONSE: + // do nothing + break; + + case MessageControl.TYPE_LEAVE: + // remove member. If/When that member comes back its + // packetIds will have been reset + incomingPacketsLastAck.remove(message.getFromHostPort()); + break; + + default: + break; + } + } + controlMessages.clear(); + + if (joinReponse){ + sendJoinResponse(); + } + if (pingReponse){ + sendPingResponse(); + } + } + + /** + * Say 'Leaving' and shutdown. + */ + public void shutdown() { + sendLeave(); + listener.shutdown(); + } + + /** + * Startup listeners and 'Join'. + */ + public void startup(ClusterManager clusterManager) { + this.clusterManager = clusterManager; + listener.startListening(); + + this.managerThread.setDaemon(true); + this.managerThread.start(); + + sendJoin(); + } + + protected SpiEbeanServer getEbeanServer(String serverName) { + return (SpiEbeanServer) clusterManager.getServer(serverName); + } + + private void sendJoin() { + sendControlMessage(true, MessageControl.TYPE_JOIN); + } + + private void sendLeave() { + sendControlMessage(false, MessageControl.TYPE_LEAVE); + } + + private void sendJoinResponse() { + sendControlMessage(true, MessageControl.TYPE_JOINRESPONSE); + } + + private void sendPingResponse() { + sendControlMessage(true, MessageControl.TYPE_PINGRESPONSE); + } + + private void sendPing() { + sendControlMessage(true, MessageControl.TYPE_PING); + } + + private void sendControlMessage(boolean requiresAck, short controlType) { + sendMessage(requiresAck, new MessageControl(controlType, localSenderHostPort)); + } + + private void sendMessage(boolean requiresAck, Message msg) { + ArrayList messages = new ArrayList(1); + messages.add(msg); + sendMessages(requiresAck, messages); + } + + private boolean sendMessages(boolean requiresAck, List messages) { + + synchronized (managerThread) { + try { + + List packets = packetWriter.write(requiresAck, messages); + sendPackets(requiresAck, packets); + return true; + + } catch (IOException e) { + String msg = "Error sending Messages " + messages; + logger.log(Level.SEVERE, msg, e); + return false; + } + } + } + + private boolean sendPackets(boolean requiresAck, List packets) throws IOException { + if (currentGroupSize == 0 && !sendWithNoMembers) { + // no other members online so not sending packets + return false; + + } else { + if (requiresAck){ + // cache them until they have been ACK'ed + outgoingPacketsCache.registerPackets(packets); + } + totalPacketsSent += packets.size(); + totalBytesSent += localSender.sendPackets(packets); + + lastSendTime = System.currentTimeMillis(); + + return true; + } + } + + /** + * Send the remoteTransEvent to all the other members of the cluster. + */ + public void broadcast(RemoteTransactionEvent remoteTransEvent) { + + synchronized (managerThread) { + try { + List packets = packetWriter.write(remoteTransEvent); + if (sendPackets(true, packets)){ + ++totalTxnEventsSent; + } + } catch (IOException e) { + String msg = "Error sending RemoteTransactionEvent " + remoteTransEvent; + logger.log(Level.SEVERE, msg, e); + } + } + } + + /** + * Effectively set the frequency by which this manager will send out ACKs. + */ + public void setManagerSleepMillis(long managerSleepMillis) { + synchronized (managerThread) { + this.managerSleepMillis = managerSleepMillis; + } + } + + /** + * Return the frequency by which this manager will send out ACKs. + */ + public long getManagerSleepMillis() { + synchronized (managerThread) { + return managerSleepMillis; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastListener.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastListener.java new file mode 100644 index 000000000..e773d2753 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastListener.java @@ -0,0 +1,263 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.ByteArrayInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MulticastSocket; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.cluster.Packet; +import com.avaje.ebeaninternal.server.cluster.PacketTransactionEvent; + +/** + * Listens for Incoming packets. + * + * @author rbygrave + */ +public class McastListener implements Runnable { + + private static final Logger logger = Logger.getLogger(McastListener.class.getName()); + + private final McastClusterManager owner; + + private final McastPacketControl packetControl; + + private final MulticastSocket sock; + + private final Thread listenerThread; + + private final String localSenderHostPort; + + private final InetAddress group; + + private final boolean debugIgnore; + + private DatagramPacket pack; + + private byte[] receiveBuffer; + + private volatile boolean shutdown; + private volatile boolean shutdownComplete; + + private long totalPacketsReceived; + private long totalBytesReceived; + private long totalTxnEventsReceived; + + public McastListener(McastClusterManager owner, McastPacketControl packetControl, int port, String address, + int bufferSize, int timeout, String localSenderHostPort, + boolean disableLoopback, int ttl, InetAddress mcastBindAddress) { + + this.debugIgnore = GlobalProperties.getBoolean("ebean.debug.mcast.ignore", false); + + this.owner = owner; + this.packetControl = packetControl; + this.localSenderHostPort = localSenderHostPort; + this.receiveBuffer = new byte[bufferSize]; + this.listenerThread = new Thread(this, "EbeanClusterMcastListener"); + + String msg = "Cluster Multicast Listening address["+address+"] port["+port+"] disableLoopback["+disableLoopback+"]"; + if (ttl >= 0){ + msg +=" ttl["+ttl+"]"; + } + if (mcastBindAddress != null){ + msg += " mcastBindAddress["+mcastBindAddress+"]"; + } + logger.info(msg); + + try { + this.group = InetAddress.getByName(address); + this.sock = new MulticastSocket(port); + this.sock.setSoTimeout(timeout); + + if (disableLoopback){ + sock.setLoopbackMode(disableLoopback); + } + + if (mcastBindAddress != null) { + // bind to a specific interface + sock.setInterface(mcastBindAddress); + } + + if (ttl >= 0) { + sock.setTimeToLive(ttl); + } + sock.setReuseAddress(true); + pack = new DatagramPacket(receiveBuffer, receiveBuffer.length); + sock.joinGroup(group); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void startListening() { + this.listenerThread.setDaemon(true); + this.listenerThread.start(); + + logger.info("Cluster Multicast Listener up and joined Group"); + } + + /** + * Shutdown this listener. + */ + public void shutdown() { + + shutdown = true; + synchronized (listenerThread) { + try { + // wait max 20 seconds + listenerThread.wait(20000); + } catch (InterruptedException e) { + logger.info("InterruptedException:"+e); + } + } + + if (!shutdownComplete){ + String msg = "WARNING: Shutdown of McastListener did not complete?"; + System.err.println(msg); + logger.warning(msg); + } + + try { + sock.leaveGroup(group); + } catch (IOException e) { + // send to syserr in case logging already shutdown + e.printStackTrace(); + String msg = "Error leaving Multicast group"; + logger.log(Level.INFO, msg, e); + } + try { + sock.close(); + } catch (Exception e) { + // send to syserr in case logging already shutdown + e.printStackTrace(); + String msg = "Error closing Multicast socket"; + logger.log(Level.INFO, msg, e); + } + } + + public void run() { + while (!shutdown) { + try { + pack.setLength(receiveBuffer.length); + sock.receive(pack); + + InetSocketAddress senderAddr = (InetSocketAddress)pack.getSocketAddress(); + + String senderHostPort = senderAddr.getAddress().getHostAddress()+":"+senderAddr.getPort(); + + if (senderHostPort.equals(localSenderHostPort)){ + if (debugIgnore || logger.isLoggable(Level.FINE)){ + logger.info("Ignoring message as sent by localSender: "+localSenderHostPort); + } + } else { + + byte[] data = pack.getData(); + + + ByteArrayInputStream bi = new ByteArrayInputStream(data); + DataInputStream dataInput = new DataInputStream(bi); + + ++totalPacketsReceived; + totalBytesReceived += pack.getLength(); + + Packet header = Packet.readHeader(dataInput); + + long packetId = header.getPacketId(); + boolean ackMsg = packetId == 0; + + boolean processThisPacket = ackMsg || packetControl.isProcessPacket(senderHostPort, header.getPacketId()); + + if (!processThisPacket){ + if (debugIgnore || logger.isLoggable(Level.FINE)){ + logger.info("Already processed packet: "+header.getPacketId()+" type:"+header.getPacketType()+" len:"+data.length); + } + } else { + if (logger.isLoggable(Level.FINER)){ + logger.info("Incoming packet:"+header.getPacketId()+" type:"+header.getPacketType()+" len:"+data.length); + } + processPacket(senderHostPort, header, dataInput); + } + } + + } catch (java.net.SocketTimeoutException e) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "timeout", e); + } + packetControl.onListenerTimeout(); + + } catch (IOException e) { + logger.log(Level.INFO, "error ?", e); + } + } + + shutdownComplete = true; + + synchronized (listenerThread) { + listenerThread.notifyAll(); + } + } + + protected void processPacket(String senderHostPort, Packet header, DataInput dataInput) { + try { + switch (header.getPacketType()) { + case Packet.TYPE_MESSAGES: + packetControl.processMessagesPacket(senderHostPort, header, dataInput, + totalPacketsReceived, totalBytesReceived, totalTxnEventsReceived); + break; + + case Packet.TYPE_TRANSEVENT: + ++totalTxnEventsReceived; + processTransactionEventPacket(header, dataInput); + break; + + default: + String msg = "Unknown Packet type:" + header.getPacketType(); + logger.log(Level.SEVERE, msg); + break; + } + } catch (IOException e) { + // need to ask to get this packet resent... + String msg = "Error reading Packet " + header.getPacketId() + " type:" + header.getPacketType(); + logger.log(Level.SEVERE, msg, e); + } + } + + private void processTransactionEventPacket(Packet header, DataInput dataInput) throws IOException { + + SpiEbeanServer server = owner.getEbeanServer(header.getServerName()); + + PacketTransactionEvent tranEventPacket = PacketTransactionEvent.forRead(header, server); + tranEventPacket.read(dataInput); + + server.remoteTransactionEvent(tranEventPacket.getEvent()); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastPacketControl.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastPacketControl.java new file mode 100644 index 000000000..ec67b2f4e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastPacketControl.java @@ -0,0 +1,144 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.DataInput; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.cluster.Packet; +import com.avaje.ebeaninternal.server.cluster.PacketMessages; + +/** + * Helps co-ordinate Packet information between the McastListener and the + * McastClusterManager. + * + * @author rbygrave + */ +public class McastPacketControl { + + private static final Logger logger = Logger.getLogger(McastPacketControl.class.getName()); + + private final String localSenderHostPort; + + private final McastClusterManager owner; + + private final HashSet groupMembers = new HashSet(); + + private final OutgoingPacketsAcked outgoingPacketsAcked = new OutgoingPacketsAcked(); + + private final IncomingPacketsProcessed incomingPacketsProcessed; + + public McastPacketControl(McastClusterManager owner, String localSenderHostPort, int maxResendIncoming) { + this.owner = owner; + this.localSenderHostPort = localSenderHostPort; + this.incomingPacketsProcessed = new IncomingPacketsProcessed(maxResendIncoming); + } + + /** + * Handle special case where cluster doesn't have any members and we don't + * get any responses. Need to tell the sender side that the group size is 0. + */ + protected void onListenerTimeout() { + if (groupMembers.size() == 0) { + owner.fromListenerTimeoutNoMembers(); + } + } + + protected void processMessagesPacket(String senderHostPort, Packet header, DataInput dataInput, + long totalPacketsReceived, long totalBytesReceived, long totalTransEventsReceived) throws IOException { + + PacketMessages packetMessages = PacketMessages.forRead(header); + packetMessages.read(dataInput); + List messages = packetMessages.getMessages(); + + if (logger.isLoggable(Level.FINER)) { + logger.finer("INCOMING Messages " + messages); + } + // messages are for all nodes in the cluster so + // we need to filter looking for messages pertaining + // to this (senderHostPort) + + MessageControl control = null; + MessageAck ack = null; + MessageResend resend = null; + + // filter for relevant messages to this node + for (int i = 0; i < messages.size(); i++) { + Message message = messages.get(i); + if (message.isControlMessage()) { + // any 'control' message is interesting + control = (MessageControl) message; + + } else if (localSenderHostPort.equals(message.getToHostPort())) { + if (message instanceof MessageAck) { + ack = (MessageAck) message; + } else if (message instanceof MessageResend) { + resend = (MessageResend) message; + } else { + logger.log(Level.SEVERE, "Expecting a MessageAck or MessageResend but got a " + + message.getClass().getName()); + } + } + } + + if (control != null) { + if (control.getControlType() == MessageControl.TYPE_LEAVE) { + groupMembers.remove(senderHostPort); + logger.info("Cluster member leaving [" + senderHostPort + "] " + groupMembers.size() + + " other members left"); + outgoingPacketsAcked.removeMember(senderHostPort); + incomingPacketsProcessed.removeMember(senderHostPort); + } else { + groupMembers.add(senderHostPort); + } + } + + long newMin = 0; + if (ack != null) { + newMin = outgoingPacketsAcked.receivedAck(senderHostPort, ack); + } + + if (newMin > 0 || control != null || resend != null) { + int groupSize = groupMembers.size(); + // synchronised on the managerThread + owner.fromListener(newMin, control, resend, groupSize, + totalPacketsReceived, totalBytesReceived, totalTransEventsReceived); + } + } + + /** + * Return true if we should process this packet. Return false if we have + * already processed the packet. + */ + public boolean isProcessPacket(String memberKey, long packetId) { + + return incomingPacketsProcessed.isProcessPacket(memberKey, packetId); + } + + public AckResendMessages getAckResendMessages(IncomingPacketsLastAck lastAck) { + + return incomingPacketsProcessed.getAckResendMessages(lastAck); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastSender.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastSender.java new file mode 100644 index 000000000..a9830ecaf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastSender.java @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.cluster.Packet; + +/** + * Handles the sending of Packets via DatagramPacket. + * + * @author rbygrave + */ +public class McastSender { + + private static final Logger logger = Logger.getLogger(McastSender.class.getName()); + + private final int port; + + private final InetAddress inetAddress; + + private final DatagramSocket sock; + + private final InetSocketAddress sendAddr; + + private final String senderHostPort; + + + public McastSender(int port, String address, int sendPort, String sendAddress) { + + try { + this.port = port; + this.inetAddress = InetAddress.getByName(address); + + InetAddress sendInetAddress = null; + if (sendAddress != null) { + sendInetAddress = InetAddress.getByName(sendAddress); + } else { + sendInetAddress = InetAddress.getLocalHost(); + } + + if (sendPort > 0) { + this.sock = new DatagramSocket(sendPort, sendInetAddress); + } else { + this.sock = new DatagramSocket(new InetSocketAddress(sendInetAddress, 0)); + } + + String msg = "Cluster Multicast Sender on["+sendInetAddress.getHostAddress()+":"+sock.getLocalPort()+"]"; + logger.info(msg); + + this.sendAddr = new InetSocketAddress(sendInetAddress, sock.getLocalPort()); + this.senderHostPort = sendInetAddress.getHostAddress()+":"+sock.getLocalPort(); + + } catch (Exception e) { + String msg = "McastSender port:" + port + " sendPort:" + sendPort + " " + address; + throw new RuntimeException(msg, e); + } + } + + /** + * Return the send Address so that if we have loopback messages we can + * detect if they where sent by this local sender and hence should be + * ignored. + */ + public InetSocketAddress getAddress() { + return sendAddr; + } + + /** + * Return the Host and Port of the sender. This is used to uniquely identify + * this instance in the cluster. + */ + public String getSenderHostPort() { + return senderHostPort; + } + + /** + * Send the packet. + */ + public int sendPacket(Packet packet) throws IOException { + + byte[] pktBytes = packet.getBytes(); + + if (logger.isLoggable(Level.FINE)){ + logger.fine("OUTGOING packet: " + packet.getPacketId() + " size:" + pktBytes.length); + } + + if (pktBytes.length > 65507){ + logger.warning("OUTGOING packet: " + packet.getPacketId() + " size:" + pktBytes.length + +" likely to be truncated using UDP with a MAXIMUM length of 65507"); + } + + DatagramPacket pack = new DatagramPacket(pktBytes, pktBytes.length, inetAddress, port); + sock.send(pack); + + return pktBytes.length; + } + + /** + * Send the list of Packets. + */ + public int sendPackets(List packets) throws IOException { + + int totalBytes = 0; + for (int i = 0; i < packets.size(); i++) { + totalBytes += sendPacket(packets.get(i)); + } + return totalBytes; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastStatus.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastStatus.java new file mode 100644 index 000000000..472512e59 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/McastStatus.java @@ -0,0 +1,155 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +/** + * Gives an overall status of this Cluster instance. + *

+ * Ideally you want to see relatively low Re-send statistics. + *

+ * + * @author rbygrave + * + */ +public class McastStatus { + + private final long totalTxnEventsSent; + private final long totalTxnEventsReceived; + + private final long totalPacketsSent; + private final long totalPacketsResent; + private final long totalPacketsReceived; + + private final long totalBytesSent; + private final long totalBytesResent; + private final long totalBytesReceived; + + private final int currentGroupSize; + private final int outgoingPacketsCacheSize; + + private final long currentPacketId; + private final long minAckedPacketId; + private final String lastOutgoingAcks; + + public String getSummary() { + + StringBuilder sb = new StringBuilder(80); + sb.append("txnOut:").append(totalTxnEventsSent).append("; "); + sb.append("txnIn:").append(totalTxnEventsReceived).append("; "); + sb.append("outPackets:").append(totalPacketsSent).append("; "); + sb.append("outBytes:").append(totalBytesSent).append("; "); + sb.append("inPackets:").append(totalPacketsReceived).append("; "); + sb.append("inBytes:").append(totalBytesReceived).append("; "); + sb.append("resentPackets:").append(totalPacketsResent).append("; "); + sb.append("resentBytes:").append(totalBytesResent).append("; "); + sb.append("groupSize:").append(currentGroupSize).append("; "); + sb.append("cache:").append(outgoingPacketsCacheSize).append("; "); + sb.append("currentPacket:").append(currentPacketId).append("; "); + sb.append("minAckedPacket:").append(minAckedPacketId).append("; "); + sb.append("lastAck:").append(lastOutgoingAcks).append("; "); + + return sb.toString(); + } + + public McastStatus(int currentGroupSize, + int outgoingPacketsCacheSize, + long currentPacketId, + long minAckedPacketId, + String lastOutgoingAcks, + long totalTransEventsSent, + long totalTransEventsReceived, + long totalPacketsSent, + long totalPacketsResent, + long totalPacketsReceived, + long totalBytesSent, + long totalBytesResent, + long totalBytesReceived) { + + this.currentGroupSize = currentGroupSize; + this.outgoingPacketsCacheSize = outgoingPacketsCacheSize; + this.currentPacketId = currentPacketId; + this.minAckedPacketId = minAckedPacketId; + this.lastOutgoingAcks = lastOutgoingAcks; + this.totalTxnEventsSent = totalTransEventsSent; + this.totalTxnEventsReceived = totalTransEventsReceived; + this.totalPacketsSent = totalPacketsSent; + this.totalPacketsResent = totalPacketsResent; + this.totalPacketsReceived = totalPacketsReceived; + + this.totalBytesSent = totalBytesSent; + this.totalBytesResent = totalBytesResent; + this.totalBytesReceived = totalBytesReceived; + + } + + + public long getTotalTxnEventsReceived() { + return totalTxnEventsReceived; + } + + public long getTotalPacketsReceived() { + return totalPacketsReceived; + } + + public long getTotalBytesSent() { + return totalBytesSent; + } + + public long getTotalBytesResent() { + return totalBytesResent; + } + + public long getTotalBytesReceived() { + return totalBytesReceived; + } + + public String getLastOutgoingAcks() { + return lastOutgoingAcks; + } + + public int getOutgoingPacketsCacheSize() { + return outgoingPacketsCacheSize; + } + + public long getCurrentPacketId() { + return currentPacketId; + } + + public long getMinAckedPacketId() { + return minAckedPacketId; + } + + public long getTotalTxnEventsSent() { + return totalTxnEventsSent; + } + + public long getTotalPacketsSent() { + return totalPacketsSent; + } + + public long getTotalPacketsResent() { + return totalPacketsResent; + } + + public long getCurrentGroupSize() { + return currentGroupSize; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/Message.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/Message.java new file mode 100644 index 000000000..e44775489 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/Message.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.IOException; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public interface Message { + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException; + + public boolean isControlMessage(); + + public String getToHostPort(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageAck.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageAck.java new file mode 100644 index 000000000..1eaca1a7f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageAck.java @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public class MessageAck implements Message { + + private final String toHostPort; + + private final long gotAllPacketId; + + public MessageAck(String toHostPort, long gotAllPacketId) { + this.toHostPort = toHostPort; + this.gotAllPacketId = gotAllPacketId; + } + + public String toString() { + return "Ack "+toHostPort+" "+gotAllPacketId; + } + + public boolean isControlMessage() { + return false; + } + + public String getToHostPort() { + return toHostPort; + } + + public long getGotAllPacketId() { + return gotAllPacketId; + } + + + public static MessageAck readBinaryMessage(DataInput dataInput) throws IOException { + + String hostPort = dataInput.readUTF(); + long gotAllPacketId = dataInput.readLong(); + return new MessageAck(hostPort, gotAllPacketId); + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + BinaryMessage m = new BinaryMessage(toHostPort.length() * 2 + 20); + + DataOutputStream os = m.getOs(); + os.writeInt(BinaryMessage.TYPE_MSGACK); + os.writeUTF(toHostPort); + os.writeLong(gotAllPacketId); + os.flush(); + + msgList.add(m); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageControl.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageControl.java new file mode 100644 index 000000000..e455db0ee --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageControl.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public class MessageControl implements Message { + + public static final short TYPE_JOIN = 1; + public static final short TYPE_LEAVE = 2; + public static final short TYPE_PING = 3; + public static final short TYPE_JOINRESPONSE = 7; + public static final short TYPE_PINGRESPONSE = 8; + + private final short controlType; + private final String fromHostPort; + + public static MessageControl readBinaryMessage(DataInput dataInput) throws IOException { + short controlType = dataInput.readShort(); + String hostPort = dataInput.readUTF(); + return new MessageControl(controlType, hostPort); + } + + public MessageControl(short controlType, String helloFromHostPort) { + this.controlType = controlType; + this.fromHostPort = helloFromHostPort; + } + + + public String toString() { + switch (controlType) { + case TYPE_JOIN: return "Join "+fromHostPort; + case TYPE_LEAVE: return "Leave "+fromHostPort; + case TYPE_PING: return "Ping "+fromHostPort; + case TYPE_PINGRESPONSE: return "PingResponse "+fromHostPort; + + default: + throw new RuntimeException("Invalid controlType "+controlType); + } + } + + public boolean isControlMessage() { + return true; + } + + public short getControlType() { + return controlType; + } + + public String getToHostPort() { + return "*"; + } + + public String getFromHostPort() { + return fromHostPort; + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + BinaryMessage m = new BinaryMessage(fromHostPort.length() * 2 + 10); + + DataOutputStream os = m.getOs(); + os.writeInt(BinaryMessage.TYPE_MSGCONTROL); + os.writeShort(controlType); + os.writeUTF(fromHostPort); + os.flush(); + + msgList.add(m); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageResend.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageResend.java new file mode 100644 index 000000000..80f8f5f77 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/MessageResend.java @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public class MessageResend implements Message { + + private final String toHostPort; + + private final List resendPacketIds; + + public MessageResend(String toHostPort, List resendPacketIds) { + this.toHostPort = toHostPort; + this.resendPacketIds = resendPacketIds; + } + + public MessageResend(String toHostPort) { + this(toHostPort, new ArrayList(4)); + } + + public String toString() { + return "Resend "+toHostPort+" "+resendPacketIds; + } + + public boolean isControlMessage() { + return false; + } + + public String getToHostPort() { + return toHostPort; + } + + public void add(long packetId){ + resendPacketIds.add(Long.valueOf(packetId)); + } + + public List getResendPacketIds() { + return resendPacketIds; + } + + public static MessageResend readBinaryMessage(DataInput dataInput) throws IOException { + + String hostPort = dataInput.readUTF(); + + MessageResend msg = new MessageResend(hostPort); + + int numberOfPacketIds = dataInput.readInt(); + for (int i = 0; i < numberOfPacketIds; i++) { + long packetId = dataInput.readLong(); + msg.add(packetId); + } + + return msg; + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + BinaryMessage m = new BinaryMessage(toHostPort.length() * 2 + 20); + + DataOutputStream os = m.getOs(); + os.writeInt(BinaryMessage.TYPE_MSGRESEND); + os.writeUTF(toHostPort); + os.writeInt(resendPacketIds.size()); + for (int i = 0; i < resendPacketIds.size(); i++) { + Long packetId = resendPacketIds.get(i); + os.writeLong(packetId.longValue()); + } + os.flush(); + msgList.add(m); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsAcked.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsAcked.java new file mode 100644 index 000000000..28a5f1bd4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsAcked.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.util.HashMap; +import java.util.Map; + +public class OutgoingPacketsAcked { + + private long minimumGotAllPacketId; + + private Map recievedByMap = new HashMap(); + + public int getGroupSize() { + synchronized (this) { + return recievedByMap.size(); + } + } + + public long getMinimumGotAllPacketId() { + synchronized (this) { + return minimumGotAllPacketId; + } + } + + public void removeMember(String groupMember){ + synchronized (this) { + recievedByMap.remove(groupMember); + resetGotAllMin(); + } + } + + private boolean resetGotAllMin() { + + long tempMin; + if (recievedByMap.isEmpty()){ + //System.out.println(" -- -- -- -- "+recievedByMap.isEmpty()); + tempMin = Long.MAX_VALUE; + } else { + tempMin = Long.MAX_VALUE; + } + + for (GroupMemberAck groupMemAck : recievedByMap.values()) { + long memberMin = groupMemAck.getGotAllPacketId(); + if (memberMin < tempMin){ + //System.out.println(" -- new tmpMin "+memberMin); + tempMin = memberMin; + } + } + + if (tempMin != minimumGotAllPacketId) { + minimumGotAllPacketId = tempMin; + return true; + } else { + return false; + } + } + + public long receivedAck(String groupMember, MessageAck ack) { + + synchronized (this) { + + boolean checkMin = false; + + GroupMemberAck groupMemberAck = recievedByMap.get(groupMember); + if (groupMemberAck == null) { + //System.out.println(" -- new groupMemberAck"); + groupMemberAck = new GroupMemberAck(); + groupMemberAck.setIfBigger(ack.getGotAllPacketId()); + recievedByMap.put(groupMember, groupMemberAck); + checkMin = true; + } else { + checkMin = groupMemberAck.getGotAllPacketId() == minimumGotAllPacketId; + //System.out.println(" -- existing groupMemberAck, checkMin:"+checkMin+" "+groupMemberAck.getGotAllPacketId()); + groupMemberAck.setIfBigger(ack.getGotAllPacketId()); + } + + boolean minChanged = false; + + //System.out.println(" -- checkMin:"+checkMin+" minimumGotAllPacketId:"+minimumGotAllPacketId); + if (checkMin || minimumGotAllPacketId == 0){ + + minChanged = resetGotAllMin(); + //System.out.println(" -- minChanged:"+minChanged+" minimumGotAllPacketId:"+minimumGotAllPacketId); + } + + return minChanged ? minimumGotAllPacketId : 0; + } + } + + private static class GroupMemberAck { + + private long gotAllPacketId; + + private GroupMemberAck() { + } + + private long getGotAllPacketId() { + return gotAllPacketId; + } + + private void setIfBigger(long newGotAll) { + if (newGotAll > gotAllPacketId) { + gotAllPacketId = newGotAll; + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsCache.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsCache.java new file mode 100644 index 000000000..a002f1602 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/mcast/OutgoingPacketsCache.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.mcast; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import com.avaje.ebeaninternal.server.cluster.Packet; + +/** + * Cache of the outgoing packets. + *

+ * These are held until we receive ACKs from the other members of the cluster to + * say they have received the packets. + *

+ * + * @author rbygrave + * + */ +public class OutgoingPacketsCache { + + private final Map packetMap = new TreeMap(); + + public int size() { + return packetMap.size(); + } + + public Packet getPacket(Long packetId) { + return packetMap.get(packetId); + } + + public String toString() { + return packetMap.keySet().toString(); + } + + /** + * Remove the packet when we give up trying to send it out. + */ + public void remove(Packet packet) { + packetMap.remove(packet.getPacketId()); + } + + public void registerPackets(List packets) { + for (int i = 0; i < packets.size(); i++) { + Packet p = packets.get(i); + packetMap.put(p.getPacketId(), p); + } + } + + public int trimAll() { + int size = packetMap.size(); + packetMap.clear(); + return size; + } + + public void trimAcknowledgedMessages(long minAcked) { + Iterator it = packetMap.keySet().iterator(); + while (it.hasNext()) { + Long pktId = it.next(); + if (minAcked >= pktId.longValue()) { + it.remove(); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/package.html b/src/main/java/com/avaje/ebeaninternal/server/cluster/package.html new file mode 100644 index 000000000..66d055461 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/package.html @@ -0,0 +1,12 @@ + + + + AvajeLib + + +Clustering service for an application. +

+A framework for supporting clustering of servers. +

+ + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/RequestProcessor.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/RequestProcessor.java new file mode 100644 index 000000000..d4a286a49 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/RequestProcessor.java @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.socket; + +import java.io.IOException; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This parses and dispatches a request to the appropriate handler. + *

+ * Looks up the appropriate RequestHandler + * and then gets it to process the Client request.

+ *

+ * Note that this is a Runnable because it is assigned to the ThreadPool. + */ +class RequestProcessor implements Runnable { + + private static final Logger logger = Logger.getLogger(RequestProcessor.class.getName()); + + private final Socket clientSocket; + + private final SocketClusterBroadcast owner; + + /** + * Create including the Listener (used to lookup the Request Handler) and + * the socket itself. + */ + public RequestProcessor(SocketClusterBroadcast owner, Socket clientSocket) { + this.clientSocket = clientSocket; + this.owner = owner; + } + + /** + * This will parse out the command. Lookup the appropriate Handler and + * pass the information to the handler for processing. + *

Dev Note: the command parsing is processed here so that it is preformed + * by the assigned thread rather than the listeners thread.

+ */ + public void run() { + try { + SocketConnection sc = new SocketConnection(clientSocket); + + while(true){ + if (owner.process(sc)) { + // got the offline message or timeout + break; + } + } + sc.disconnect(); + + } catch (IOException e) { + logger.log(Level.SEVERE, null, e); + } catch (ClassNotFoundException e) { + logger.log(Level.SEVERE, null, e); + } + } + + +}; diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClient.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClient.java new file mode 100644 index 000000000..f79e10590 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClient.java @@ -0,0 +1,151 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.socket; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * The client side of the socket clustering. + */ +class SocketClient { + + private static final Logger logger = Logger.getLogger(SocketClient.class.getName()); + + private final InetSocketAddress address; + + private final String hostPort; + + private boolean online; + + private Socket socket; + private OutputStream os; + private ObjectOutputStream oos; + + /** + * Construct with an IP address and port. + */ + public SocketClient(InetSocketAddress address) { + this.address = address; + this.hostPort = address.getHostName()+":"+address.getPort(); + } + + public String getHostPort() { + return hostPort; + } + + public int getPort() { + return address.getPort(); + } + + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) throws IOException { + if (online){ + setOnline(); + } else { + disconnect(); + } + } + + + /** + * Set whether the client is thought to be online. + */ + private void setOnline() throws IOException { + connect(); + this.online = true; + } + + public void reconnect() throws IOException { + disconnect(); + connect(); + } + + private void connect() throws IOException { + if (socket != null){ + throw new IllegalStateException("Already got a socket connection?"); + } + Socket s = new Socket(); + s.setKeepAlive(true); + s.connect(address); + + this.socket = s; + this.os = socket.getOutputStream(); + } + + public void disconnect() { + this.online = false; + if (socket != null){ + + try { + socket.close(); + } catch (IOException e) { + String msg = "Error disconnecting from Cluster member "+hostPort; + logger.log(Level.INFO, msg, e); + } + + os = null; + oos = null; + socket = null; + } + } + + public boolean register(SocketClusterMessage registerMsg) { + + try { + setOnline(); + send(registerMsg); + return true; + } catch (IOException e) { + disconnect(); + return false; + } + } + + public boolean send(SocketClusterMessage msg) throws IOException { + + if (online){ + writeObject(msg); + return true; + + } else { + return false; + } + + } + + private void writeObject(Object object) throws IOException { + if (oos == null){ + this.oos = new ObjectOutputStream(os); + } + oos.writeObject(object); + oos.flush(); + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterBroadcast.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterBroadcast.java new file mode 100644 index 000000000..b2f403636 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterBroadcast.java @@ -0,0 +1,265 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.socket; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.cluster.ClusterBroadcast; +import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.cluster.DataHolder; +import com.avaje.ebeaninternal.server.cluster.SerialiseTransactionHelper; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; + +/** + * Broadcast messages across the cluster using sockets. + */ +public class SocketClusterBroadcast implements ClusterBroadcast { + + private static final Logger logger = Logger.getLogger(SocketClusterBroadcast.class.getName()); + + private final SocketClient local; + + private final HashMap clientMap; + + private final SocketClusterListener listener; + + private SocketClient[] members; + + private ClusterManager clusterManager; + + private final TxnSerialiseHelper txnSerialiseHelper = new TxnSerialiseHelper(); + + private final AtomicInteger txnOutgoing = new AtomicInteger(); + private final AtomicInteger txnIncoming = new AtomicInteger(); + + + public SocketClusterBroadcast( ){ + + String localHostPort = GlobalProperties.get("ebean.cluster.local", null); + String members = GlobalProperties.get("ebean.cluster.members", null); + + logger.info("Clustering using Sockets local["+localHostPort+"] members["+members+"]"); + + this.local = new SocketClient(parseFullName(localHostPort)); + this.clientMap = new HashMap(); + + String[] memArray = StringHelper.delimitedToArray(members, ",", false); + for (int i = 0; i < memArray.length; i++) { + InetSocketAddress member = parseFullName(memArray[i]); + SocketClient client = new SocketClient(member); + if (!local.getHostPort().equalsIgnoreCase(client.getHostPort())) { + // don't add the local one ... + clientMap.put(client.getHostPort(), client); + } + } + + this.members = clientMap.values().toArray(new SocketClient[clientMap.size()]); + this.listener = new SocketClusterListener(this, local.getPort()); + } + + /** + * Return the current status of this instance. + */ + public SocketClusterStatus getStatus() { + + // count of online members + int currentGroupSize = 0; + for (int i = 0; i < members.length; i++) { + if (members[i].isOnline()) { + ++currentGroupSize; + } + } + int txnIn = txnIncoming.get(); + int txnOut = txnOutgoing.get(); + + return new SocketClusterStatus(currentGroupSize, txnIn, txnOut); + } + + public void startup(ClusterManager clusterManager) { + + this.clusterManager = clusterManager; + try { + listener.startListening(); + register(); + + } catch (IOException e) { + throw new PersistenceException(e); + } + } + + public void shutdown() { + deregister(); + listener.shutdown(); + } + + /** + * Register with all the other members of the Cluster. + */ + private void register() { + + SocketClusterMessage h = SocketClusterMessage.register(local.getHostPort(), true); + + for (int i = 0; i < members.length; i++) { + boolean online = members[i].register(h); + + String msg = "Cluster Member ["+members[i].getHostPort()+"] online["+online+"]"; + logger.info(msg); + } + } + + protected void setMemberOnline(String fullName, boolean online) throws IOException { + synchronized (clientMap) { + String msg = "Cluster Member ["+fullName+"] online["+online+"]"; + logger.info(msg); + SocketClient member = clientMap.get(fullName); + member.setOnline(online); + } + } + + private void send(SocketClient client, SocketClusterMessage msg) { + + try { + // alternative would be to connect/disconnect here + // but prefer to use keepalive + client.send(msg); + + } catch (Exception ex){ + logger.log(Level.SEVERE, "Error sending message", ex); + try { + client.reconnect(); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error trying to reconnect", ex); + } + } + } + + /** + * Send the payload to all the members of the cluster. + */ + public void broadcast(RemoteTransactionEvent remoteTransEvent) { + try { + + txnOutgoing.incrementAndGet(); + DataHolder dataHolder = txnSerialiseHelper.createDataHolder(remoteTransEvent); + SocketClusterMessage msg = SocketClusterMessage.transEvent(dataHolder); + broadcast(msg); + } catch (Exception e){ + String msg = "Error sending RemoteTransactionEvent "+remoteTransEvent+" to cluster members."; + logger.log(Level.SEVERE, msg, e); + } + } + + protected void broadcast(SocketClusterMessage msg) { + + for (int i = 0; i < members.length; i++) { + send(members[i], msg); + } + } + + /** + * Leave the cluster. + */ + private void deregister() { + + SocketClusterMessage h = SocketClusterMessage.register(local.getHostPort(), false); + broadcast(h); + + for (int i = 0; i < members.length; i++) { + members[i].disconnect(); + } + } + + /** + * Process a Cluster message. + */ + protected boolean process(SocketConnection request) throws IOException, ClassNotFoundException { + + try { + SocketClusterMessage h = (SocketClusterMessage)request.readObject(); + + if (h.isRegisterEvent()){ + setMemberOnline(h.getRegisterHost(), h.isRegister()); + + } else { + txnIncoming.incrementAndGet(); + DataHolder dataHolder = h.getDataHolder(); + RemoteTransactionEvent transEvent = txnSerialiseHelper.read(dataHolder); + transEvent.run(); + } + + if (h.isRegisterEvent() && !h.isRegister()){ + // instance shutting down + return true; + } else { + return false; + } + } catch (InterruptedIOException e) { + String msg = "Timeout waiting for message"; + logger.log(Level.INFO, msg, e); + try { + request.disconnect(); + } catch (IOException ex){ + logger.log(Level.INFO, "Error disconnecting after timeout", ex); + } + return true; + } + } + + + /** + * Parse a host:port into a InetSocketAddress. + */ + private InetSocketAddress parseFullName(String hostAndPort) { + + try { + hostAndPort = hostAndPort.trim(); + int colonPos = hostAndPort.indexOf(":"); + if (colonPos == -1) { + String msg = "No colon \":\" in "+hostAndPort; + throw new IllegalArgumentException(msg); + } + String host = hostAndPort.substring(0, colonPos); + String sPort = hostAndPort.substring(colonPos + 1, hostAndPort.length()); + int port = Integer.parseInt(sPort); + + return new InetSocketAddress(host, port); + + } catch (Exception ex){ + throw new RuntimeException("Error parsing ["+hostAndPort+"] for the form [host:port]", ex); + } + } + + class TxnSerialiseHelper extends SerialiseTransactionHelper { + + @Override + public SpiEbeanServer getEbeanServer(String serverName) { + return (SpiEbeanServer)clusterManager.getServer(serverName); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterListener.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterListener.java new file mode 100644 index 000000000..ba3fc6529 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterListener.java @@ -0,0 +1,181 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.socket; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.lib.thread.ThreadPool; +import com.avaje.ebeaninternal.server.lib.thread.ThreadPoolManager; + +/** + * Serverside multithreaded socket listener. Accepts connections and dispatches + * them to an appropriate handler. + *

+ * This is designed as a single port listener, where part of the connection + * protocol determines which service the client is requesting (rather than a + * port per service). + *

+ *

+ * It has its own daemon background thread that handles the accept() loop on the + * ServerSocket. + *

+ */ +class SocketClusterListener implements Runnable { + + private static final Logger logger = Logger.getLogger(SocketClusterListener.class.getName()); + + /** + * The port the SocketListener uses. + */ + private final int port; + + /** + * The length of the socket accept timeout. + */ + private final int listenTimeout = 60000; + + /** + * The server socket used to listen for requests. + */ + private final ServerSocket serverListenSocket; + + /** + * The listening thread. + */ + private final Thread listenerThread; + + /** + * The pool of threads that actually do the parsing execution of requests. + */ + private final ThreadPool threadPool; + + private final SocketClusterBroadcast owner; + + /** + * shutting down flag. + */ + boolean doingShutdown; + + /** + * Whether the listening thread is busy assigning a request to a thread. + */ + boolean isActive; + + /** + * Construct with a given thread pool name. + */ + public SocketClusterListener(SocketClusterBroadcast owner, int port) { + this.owner = owner; + this.threadPool = ThreadPoolManager.getThreadPool("EbeanClusterMember"); + this.port = port; + + try { + this.serverListenSocket = new ServerSocket(port); + this.serverListenSocket.setSoTimeout(listenTimeout); + this.listenerThread = new Thread(this, "EbeanClusterListener"); + + } catch (IOException e){ + String msg = "Error starting cluster socket listener on port "+port; + throw new RuntimeException(msg,e); + } + } + + /** + * Returns the port the listener is using. + */ + public int getPort() { + return port; + } + + /** + * Start listening for requests. + */ + public void startListening() throws IOException { + this.listenerThread.setDaemon(true); + this.listenerThread.start(); + } + + /** + * Shutdown this listener. + */ + public void shutdown() { + doingShutdown = true; + try { + if (isActive) { + synchronized (listenerThread) { + try { + listenerThread.wait(1000); + } catch (InterruptedException e) { + // OK to ignore as expected to Interrupt for shutdown. + ; + } + } + } + listenerThread.interrupt(); + serverListenSocket.close(); + } catch (IOException e) { + logger.log(Level.SEVERE, null, e); + } + } + + /** + * This is a runnable and so this must be public. Don't call this externally + * but rather call the startListening() method. + */ + public void run() { + // run in loop until doingShutdown is true... + while (!doingShutdown) { + try { + synchronized (listenerThread) { + Socket clientSocket = serverListenSocket.accept(); + + isActive = true; + + Runnable request = new RequestProcessor(owner, clientSocket); + threadPool.assign(request, true); + + isActive = false; + } + } catch (SocketException e) { + if (doingShutdown) { + String msg = "doingShutdown and accept threw:"+ e.getMessage(); + logger.info(msg); + + } else { + logger.log(Level.SEVERE, null, e); + } + + } catch (InterruptedIOException e) { + // this will happen when the server is very quiet. + // that is, no requests + logger.fine("Possibly expected due to accept timeout?" + e.getMessage()); + + } catch (IOException e) { + // log it and continue in the loop... + logger.log(Level.SEVERE, null, e); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterMessage.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterMessage.java new file mode 100644 index 000000000..47015de8b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterMessage.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.socket; + +import java.io.Serializable; + +import com.avaje.ebeaninternal.server.cluster.DataHolder; +import com.avaje.ebeaninternal.server.cluster.Packet; + +/** + * The messages broadcast around the cluster. + */ +public class SocketClusterMessage implements Serializable { + + private static final long serialVersionUID = 2993350408394934473L; + + private final String registerHost; + + private final boolean register; + + private final DataHolder dataHolder; + + public static SocketClusterMessage register(String registerHost, boolean register){ + return new SocketClusterMessage(registerHost, register); + } + + public static SocketClusterMessage transEvent(DataHolder transEvent){ + return new SocketClusterMessage(transEvent); + } + + public static SocketClusterMessage packet(Packet packet){ + DataHolder d = new DataHolder(packet.getBytes()); + return new SocketClusterMessage(d); + } + + /** + * Used to construct a Child AttributeMap. + */ + private SocketClusterMessage(String registerHost, boolean register) { + this.registerHost = registerHost; + this.register = register; + this.dataHolder = null; + } + + private SocketClusterMessage(DataHolder dataHolder) { + this.dataHolder = dataHolder; + this.registerHost = null; + this.register = false; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + if (registerHost != null){ + sb.append("register "); + sb.append(register); + sb.append(" "); + sb.append(registerHost); + } else { + sb.append("transEvent "); + } + return sb.toString(); + } + + public boolean isRegisterEvent() { + return registerHost != null; + } + + public String getRegisterHost() { + return registerHost; + } + + public boolean isRegister() { + return register; + } + + public DataHolder getDataHolder() { + return dataHolder; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterStatus.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterStatus.java new file mode 100644 index 000000000..ad4c46060 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketClusterStatus.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.socket; + +/** + * The current state of this cluster member. + * + * @author rbygrave + */ +public class SocketClusterStatus { + + private final int currentGroupSize; + private final int txnIncoming; + private final int txtOutgoing; + + public SocketClusterStatus(int currentGroupSize, int txnIncoming, int txnOutgoing) { + this.currentGroupSize = currentGroupSize; + this.txnIncoming = txnIncoming; + this.txtOutgoing = txnOutgoing; + } + + /** + * Return the number of members of the cluster currently online. + */ + public int getCurrentGroupSize() { + return currentGroupSize; + } + + /** + * Return the number of Remote transactions received. + */ + public int getTxnIncoming() { + return txnIncoming; + } + + /** + * Return the number of transactions sent to the cluster. + */ + public int getTxtOutgoing() { + return txtOutgoing; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketConnection.java b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketConnection.java new file mode 100644 index 000000000..7f25412aa --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cluster/socket/SocketConnection.java @@ -0,0 +1,146 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.cluster.socket; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * The client side of a TCP Sockect connection. + */ +class SocketConnection { + + /** + * The object underlying objectOutputStream. + */ + ObjectOutputStream oos; + + /** + * The underlying ObjectInputStream. + */ + ObjectInputStream ois; + + /** + * The underlying inputStream. + */ + InputStream is; + + /** + * The underlying outputStream. + */ + OutputStream os; + + /** + * The underlying socket. + */ + Socket socket; + + /** + * Create for a given Socket. + */ + public SocketConnection(Socket socket) throws IOException { + this.is = socket.getInputStream(); + this.os = socket.getOutputStream(); + this.socket = socket; + } + + /** + * Disconnect from the server. + */ + public void disconnect() throws IOException { + os.flush(); + socket.close(); + } + + /** + * Flush the outputStream. + */ + public void flush() throws IOException { + os.flush(); + } + + /** + * Read an object from the object input stream. + */ + public Object readObject() throws IOException, ClassNotFoundException { + return getObjectInputStream().readObject(); + } + + /** + * Write an object to the object output stream. + */ + public ObjectOutputStream writeObject(Object object) throws IOException { + ObjectOutputStream oos = getObjectOutputStream(); + oos.writeObject(object); + return oos; + } + + /** + * Get the object output stream. + */ + public ObjectOutputStream getObjectOutputStream() throws IOException { + if (oos == null){ + oos = new ObjectOutputStream(os); + } + return oos; + } + + /** + * Get the object input stream. + */ + public ObjectInputStream getObjectInputStream() throws IOException { + if (ois == null){ + ois = new ObjectInputStream(is); + } + return ois; + } + + + /** + * Set the ObjectInputStream to use. + */ + public void setObjectInputStream(ObjectInputStream ois) { + this.ois = ois; + } + + /** + * Set the ObjectOutputStream to use. + */ + public void setObjectOutputStream(ObjectOutputStream oos) { + this.oos = oos; + } + + /** + * Return the underlying input stream. + */ + public InputStream getInputStream() throws IOException { + return is; + } + + /** + * Return the underlying output stream. + */ + public OutputStream getOutputStream() throws IOException { + return os; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BasicTypeConverter.java b/src/main/java/com/avaje/ebeaninternal/server/core/BasicTypeConverter.java new file mode 100644 index 000000000..7680ede4d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BasicTypeConverter.java @@ -0,0 +1,493 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; +import java.util.UUID; + + +/** + * Default implementation of TypeConverter. + *

+ * Converts objects to the required type if required. + *

+ */ +public final class BasicTypeConverter implements Serializable { + + private static final long serialVersionUID = 7691463236204070311L; + + /** + * Type code for java.util.Calendar. + */ + public static final int UTIL_CALENDAR = -999998986; + + /** + * Type code for java.util.Date. + */ + public static final int UTIL_DATE = -999998988; + + /** + * Type code for java.math.BigInteger. + */ + public static final int MATH_BIGINTEGER = -999998987; + + /** + * Type code for an Enum type. + */ + public static final int ENUM = -999998989; + + private BasicTypeConverter() { + } + + /** + * Convert the Object to the required data type. + * + * @param value + * the Object value + * @param toDataType + * the dataType as per java.sql.Types. + */ + public static Object convert(Object value, int toDataType) { + + try { + switch (toDataType) { + case UTIL_DATE: { + return toUtilDate(value); + } + case UTIL_CALENDAR: { + return toCalendar(value); + } + case Types.BIGINT: { + return toLong(value); + } + case Types.INTEGER: { + return toInteger(value); + } + case Types.BIT: { + return toBoolean(value); + } + case Types.TINYINT: { + return toByte(value); + } + case Types.SMALLINT: { + return toShort(value); + } + case Types.NUMERIC: { + return toBigDecimal(value); + } + case Types.DECIMAL: { + return toBigDecimal(value); + } + case Types.REAL: { + return toFloat(value); + } + case Types.DOUBLE: { + return toDouble(value); + } + case Types.FLOAT: { + return toDouble(value); + } + case Types.BOOLEAN: { + return toBoolean(value); + } + case Types.TIMESTAMP: { + return toTimestamp(value); + } + case Types.DATE: { + return toDate(value); + } + case Types.VARCHAR: { + return toString(value); + } + case Types.CHAR: { + return toString(value); + } + case Types.OTHER: { + return value; + } + case Types.JAVA_OBJECT: { + return value; + } + case Types.BINARY: + case Types.LONGVARBINARY: + case Types.BLOB: { + return value; + } + case Types.LONGVARCHAR: + case Types.CLOB: { + return value; + } + default: { + String msg = "Unhandled data type [" + toDataType + "] converting [" + value + "]"; + throw new RuntimeException(msg); + } + } + } catch (ClassCastException e) { + String m = "ClassCastException converting to data type [" + toDataType + "] value [" + value + "]"; + throw new RuntimeException(m); + } + } + + /** + * Convert the value to a String. + */ + public static String toString(Object value) { + + if (value == null) { + return null; + } + if (value instanceof String) { + return (String) value; + } + if (value instanceof char[]) { + return String.valueOf((char[]) value); + } + + return value.toString(); + } + + + /** + * Convert the value to a Boolean with an explicit String true value. + */ + public static Boolean toBoolean(Object value, String dbTrueValue) { + + if (value == null) { + return null; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + String s = value.toString(); + return s.equalsIgnoreCase(dbTrueValue); + } + + /** + * Convert the value to a Boolean. Can be a Boolean or the string values + * "true" or "false". + */ + public static Boolean toBoolean(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + + return Boolean.valueOf(value.toString()); + } + + /** + * Convert the value to a UUID. + */ + public static UUID toUUID(Object value) { + + if (value == null) { + return null; + } + if (value instanceof String) { + return UUID.fromString((String) value); + } + return (UUID) value; + } + + /** + * convert the passed in object to a BigDecimal. It should be another + * numeric type. + */ + public static BigDecimal toBigDecimal(Object value) { + + if (value == null) { + return null; + } + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + return new BigDecimal(value.toString()); + } + + public static Float toFloat(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Float) { + return (Float) value; + } + if (value instanceof Number) { + return Float.valueOf(((Number) value).floatValue()); + } + return Float.valueOf(value.toString()); + } + + public static Short toShort(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Short) { + return (Short) value; + } + if (value instanceof Number) { + return Short.valueOf(((Number) value).shortValue()); + } + return Short.valueOf(value.toString()); + } + + public static Byte toByte(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Byte) { + return (Byte) value; + } + return Byte.valueOf(value.toString()); + } + + /** + * convert the passed in object to a Integer. It should be another numeric + * type. + */ + public static Integer toInteger(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Number) { + return Integer.valueOf(((Number) value).intValue()); + } + return Integer.valueOf(value.toString()); + } + + /** + * Convert the object to a Long. It should be another numeric type. + */ + public static Long toLong(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Long) { + return (Long) value; + } + if (value instanceof String) { + return Long.valueOf((String) value); + } + if (value instanceof Number) { + return Long.valueOf(((Number) value).longValue()); + } + if (value instanceof java.util.Date) { + return Long.valueOf(((java.util.Date) value).getTime()); + } + if (value instanceof Calendar) { + return Long.valueOf(((Calendar) value).getTime().getTime()); + } + return Long.valueOf(value.toString()); + } + + public static BigInteger toMathBigInteger(Object value) { + + if (value == null) { + return null; + } + if (value instanceof BigInteger) { + return (BigInteger) value; + } + return new BigInteger(value.toString()); + } + + /** + * Convert the object to a Double. It should be another numberic type. + */ + public static Double toDouble(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return Double.valueOf(((Number) value).doubleValue()); + } + return Double.valueOf(value.toString()); + } + + /** + * convert the passed in object to a Timestamp. It is expected to be a + * java.sql.Date really. + */ + public static Timestamp toTimestamp(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Timestamp) { + return (Timestamp) value; + + } else if (value instanceof java.util.Date) { + // no nanos here... so hopefully ok + return new Timestamp(((java.util.Date) value).getTime()); + + } else if (value instanceof Calendar) { + return new Timestamp(((Calendar) value).getTime().getTime()); + + } else if (value instanceof String) { + return Timestamp.valueOf((String) value); + + } else if (value instanceof Number) { + return new Timestamp(((Number) value).longValue()); + + } else { + String msg = "Unable to convert [" + value.getClass().getName() + "] into a Timestamp."; + throw new RuntimeException(msg); + } + } + + public static java.sql.Time toTime(Object value) { + + if (value == null) { + return null; + } + if (value instanceof java.sql.Time) { + return (java.sql.Time) value; + + } else if (value instanceof String) { + return java.sql.Time.valueOf((String) value); + + } else { + String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date."; + throw new RuntimeException(m); + } + } + + /** + * convert the passed in object to a java sql Date. + */ + public static java.sql.Date toDate(Object value) { + + if (value == null) { + return null; + } + if (value instanceof java.sql.Date) { + return (java.sql.Date) value; + + } else if (value instanceof java.util.Date) { + return new java.sql.Date(((java.util.Date) value).getTime()); + + } else if (value instanceof Calendar) { + return new java.sql.Date(((Calendar) value).getTime().getTime()); + + } else if (value instanceof String) { + return java.sql.Date.valueOf((String) value); + + } else if (value instanceof Number) { + return new java.sql.Date(((Number) value).longValue()); + + } else { + String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date."; + throw new RuntimeException(m); + } + } + + /** + * convert the passed in object to a java sql Date. + */ + public static java.util.Date toUtilDate(Object value) { + + if (value == null) { + return null; + } + if (value instanceof java.sql.Timestamp) { + // loss of nanos precision + return new java.util.Date(((java.sql.Timestamp) value).getTime()); + } + // DEVNOTE: strictly speaking do I need to convert a java.sql.Date to + // java.util.Date? equals() is symmetrical so perhaps this is not + // really required? + if (value instanceof java.sql.Date) { + return new java.util.Date(((java.sql.Date) value).getTime()); + } + if (value instanceof java.util.Date) { + return (java.util.Date) value; + + } else if (value instanceof Calendar) { + return ((Calendar) value).getTime(); + + } else if (value instanceof String) { + return new java.util.Date(Timestamp.valueOf((String) value).getTime()); + + } else if (value instanceof Number) { + return new java.util.Date(((Number) value).longValue()); + + } else { + throw new RuntimeException("Unable to convert [" + value.getClass().getName() + "] into a java.util.Date"); + } + } + + /** + * convert the passed in object to a java sql Date. + */ + public static Calendar toCalendar(Object value) { + + if (value == null) { + return null; + } + if (value instanceof Calendar) { + return (Calendar) value; + + } else if (value instanceof java.util.Date) { + java.util.Date date = ((java.util.Date) value); + return toCalendarFromDate(date); + + } else if (value instanceof String) { + java.util.Date date = toUtilDate(value); + return toCalendarFromDate(date); + + } else if (value instanceof Number) { + long timeMillis = ((Number) value).longValue(); + java.util.Date date = new java.util.Date(timeMillis); + return toCalendarFromDate(date); + + } else { + String m = "Unable to convert [" + value.getClass().getName() + "] into a java.util.Date"; + throw new RuntimeException(m); + } + } + + private static Calendar toCalendarFromDate(java.util.Date date) { + + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + + return cal; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java new file mode 100644 index 000000000..3632c7081 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java @@ -0,0 +1,146 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.sql.Connection; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.LogLevel; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; + +/** + * Base class for find and persist requests. + */ +public abstract class BeanRequest { + + /** + * The server processing the request. + */ + final SpiEbeanServer ebeanServer; + + final String serverName; + + /** + * The transaction this is part of. + */ + SpiTransaction transaction; + + boolean createdTransaction; + + boolean readOnly; + + public BeanRequest(SpiEbeanServer ebeanServer, SpiTransaction t) { + this.ebeanServer = ebeanServer; + this.serverName = ebeanServer.getName(); + this.transaction = t; + } + + /** + * initialise an implicit transaction if one is not currently supplied. + *

+ * A transaction may have been passed in or active in the thread local. If + * not then create one implicitly to handle the request. + *

+ */ + public abstract void initTransIfRequired(); + + /** + * A helper method for creating an implicit transaction is it is required. + *

+ * A transaction may have been passed in or active in the thread local. If + * not then create one implicitly to handle the request. + *

+ */ + public void createImplicitTransIfRequired(boolean readOnlyTransaction) { + if (transaction == null) { + transaction = ebeanServer.getCurrentServerTransaction(); + if (transaction == null || !transaction.isActive()) { + // create an implicit transaction to execute this query + transaction = ebeanServer.createServerTransaction(false, -1); + // commented out for performance reasons... + // TODO: review performance of trans.setReadOnly(true) + //if (readOnlyTransaction) { + // readOnly = true; + // transaction.setReadOnly(true); + //} + createdTransaction = true; + } + } + } + + /** + * Commit this transaction if it was created for this request. + */ + public void commitTransIfRequired() { + if (createdTransaction) { + if (readOnly) { + transaction.rollback(); + } else { + transaction.commit(); + } + } + } + + /** + * Rollback the transaction if it was created for this request. + */ + public void rollbackTransIfRequired() { + if (createdTransaction) { + transaction.rollback(); + } + } + + /** + * Return the server processing the request. Made available for + * BeanController and BeanFinder. + */ + public EbeanServer getEbeanServer() { + return ebeanServer; + } + + /** + * Return the Transaction associated with this request. + */ + public SpiTransaction getTransaction() { + return transaction; + } + + /** + * Returns the connection from the Transaction. + */ + public Connection getConnection() { + return transaction.getInternalConnection(); + } + + /** + * Return true if SQL should be logged for this transaction. + */ + public boolean isLogSql() { + return transaction.getLogLevel().ordinal() >= LogLevel.SQL.ordinal(); + } + + /** + * Return true if SUMMARY information should be logged for this transaction. + */ + public boolean isLogSummary() { + return transaction.getLogLevel().ordinal() >= LogLevel.SUMMARY.ordinal(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java new file mode 100644 index 000000000..140973c58 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.util.ClassPathSearch; +import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter; + +/** + * Searches for interesting classes such as Entities, Embedded and ScalarTypes. + */ +public class BootupClassPathSearch { + + private static final Logger logger = Logger.getLogger(BootupClassPathSearch.class.getName()); + + private final Object monitor = new Object(); + + private final ClassLoader classLoader; + + private final List packages; + private final List jars; + + private BootupClasses bootupClasses; + + /** + * Construct and search for interesting classes. + */ + public BootupClassPathSearch(ClassLoader classLoader, List packages, List jars) { + this.classLoader = (classLoader == null) ? getClass().getClassLoader() : classLoader; + this.packages = packages; + this.jars = jars; + } + + public BootupClasses getBootupClasses() { + synchronized (monitor) { + + if (bootupClasses == null){ + bootupClasses = search(); + } + + return bootupClasses; + } + } + + /** + * Search the classPath for the classes we are interested in. + */ + private BootupClasses search() { + synchronized (monitor) { + try { + + BootupClasses bc = new BootupClasses(); + + long st = System.currentTimeMillis(); + + ClassPathSearchFilter filter = createFilter(); + + ClassPathSearch finder = new ClassPathSearch(classLoader, filter, bc); + + finder.findClasses(); + Set jars = finder.getJarHits(); + Set pkgs = finder.getPackageHits(); + + long searchTime = System.currentTimeMillis() - st; + + String msg = "Classpath search hits in jars" + jars + " pkgs" + pkgs + " searchTime[" + searchTime+ "]"; + logger.info(msg); + + return bc; + + } catch (Exception ex) { + String msg = "Error in classpath search (looking for entities etc)"; + throw new RuntimeException(msg, ex); + } + } + } + + private ClassPathSearchFilter createFilter() { + + ClassPathSearchFilter filter = new ClassPathSearchFilter(); + filter.addDefaultExcludePackages(); + + if (packages != null) { + for (String packageName : packages) { + filter.includePackage(packageName); + } + } + + if (jars != null) { + for (String jarName : jars) { + filter.includeJar(jarName); + } + } + + return filter; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java new file mode 100644 index 000000000..197d6a7c8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java @@ -0,0 +1,460 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +import com.avaje.ebean.annotation.LdapDomain; +import com.avaje.ebean.config.CompoundType; +import com.avaje.ebean.config.ScalarTypeConverter; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.event.ServerConfigStartup; +import com.avaje.ebean.event.TransactionEventListener; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher; + +/** + * Interesting classes for a EbeanServer such as Embeddable, Entity, + * ScalarTypes, Finders, Listeners and Controllers. + */ +public class BootupClasses implements ClassPathSearchMatcher { + + private static final Logger logger = Logger.getLogger(BootupClasses.class.getName()); + + private ArrayList> xmlBeanList = new ArrayList>(); + + private ArrayList> embeddableList = new ArrayList>(); + + private ArrayList> entityList = new ArrayList>(); + + private ArrayList> scalarTypeList = new ArrayList>(); + + private ArrayList> scalarConverterList = new ArrayList>(); + + private ArrayList> compoundTypeList = new ArrayList>(); + + private ArrayList> beanControllerList = new ArrayList>(); + + private ArrayList> transactionEventListenerList = new ArrayList>(); + + private ArrayList> beanFinderList = new ArrayList>(); + + private ArrayList> beanListenerList = new ArrayList>(); + + private ArrayList> beanQueryAdapterList = new ArrayList>(); + + private ArrayList> luceneIndexList = new ArrayList>(); + + private ArrayList> serverConfigStartupList = new ArrayList>(); + private ArrayList serverConfigStartupInstances = new ArrayList(); + + private List persistControllerInstances = new ArrayList(); + private List> persistListenerInstances = new ArrayList>(); + private List queryAdapterInstances = new ArrayList(); + private List transactionEventListenerInstances = new ArrayList(); + + public BootupClasses() { + } + + public BootupClasses(List> list) { + if (list != null) { + process(list.iterator()); + } + } + + private BootupClasses(BootupClasses parent) { + this.xmlBeanList.addAll(parent.xmlBeanList); + this.embeddableList.addAll(parent.embeddableList); + this.entityList.addAll(parent.entityList); + this.scalarTypeList.addAll(parent.scalarTypeList); + this.scalarConverterList.addAll(parent.scalarConverterList); + this.compoundTypeList.addAll(parent.compoundTypeList); + this.beanControllerList.addAll(parent.beanControllerList); + this.transactionEventListenerList.addAll(parent.transactionEventListenerList); + this.beanFinderList.addAll(parent.beanFinderList); + this.beanListenerList.addAll(parent.beanListenerList); + this.beanQueryAdapterList.addAll(parent.beanQueryAdapterList); + this.luceneIndexList.addAll(parent.luceneIndexList); + this.serverConfigStartupList.addAll(parent.serverConfigStartupList); + } + + private void process(Iterator> it) { + while (it.hasNext()) { + Class cls = it.next(); + isMatch(cls); + } + } + + /** + * Create a copy of this object so that classes can be added to it. + */ + public BootupClasses createCopy() { + return new BootupClasses(this); + } + + /** + * Run any ServerConfigStartup listeners. + */ + public void runServerConfigStartup(ServerConfig serverConfig) { + + for (Class cls : serverConfigStartupList) { + try { + ServerConfigStartup newInstance = (ServerConfigStartup) cls.newInstance(); + newInstance.onStart(serverConfig); + + } catch (Exception e) { + String msg = "Error creating BeanQueryAdapter " + cls; + logger.log(Level.SEVERE, msg, e); + } + } + } + + public void addQueryAdapters(List queryAdapterInstances) { + if (queryAdapterInstances != null) { + for (BeanQueryAdapter a : queryAdapterInstances) { + this.queryAdapterInstances.add(a); + // don't automatically instantiate + this.beanQueryAdapterList.remove(a.getClass()); + } + } + } + + /** + * Add BeanPersistController instances. + */ + public void addPersistControllers(List beanControllerInstances) { + if (beanControllerInstances != null) { + for (BeanPersistController c : beanControllerInstances) { + this.persistControllerInstances.add(c); + // don't automatically instantiate + this.beanControllerList.remove(c.getClass()); + } + } + } + + /** + * Add TransactionEventListeners instances. + */ + public void addTransactionEventListeners(List transactionEventListeners) { + if (transactionEventListeners != null) { + for (TransactionEventListener c : transactionEventListeners) { + this.transactionEventListenerInstances.add(c); + // don't automatically instantiate + this.transactionEventListenerList.remove(c.getClass()); + } + } + } + + public void addPersistListeners(List> listenerInstances) { + if (listenerInstances != null) { + for (BeanPersistListener l : listenerInstances) { + this.persistListenerInstances.add(l); + // don't automatically instantiate + this.beanListenerList.remove(l.getClass()); + } + } + } + + public void addServerConfigStartup(List startupInstances) { + if (startupInstances != null) { + for (ServerConfigStartup l : startupInstances) { + this.serverConfigStartupInstances.add(l); + // don't automatically instantiate + this.serverConfigStartupList.remove(l.getClass()); + } + } + } + + public List getBeanQueryAdapters() { + // add class registered BeanQueryAdapter to the + // already created instances + for (Class cls : beanQueryAdapterList) { + try { + BeanQueryAdapter newInstance = (BeanQueryAdapter) cls.newInstance(); + queryAdapterInstances.add(newInstance); + } catch (Exception e) { + String msg = "Error creating BeanQueryAdapter " + cls; + logger.log(Level.SEVERE, msg, e); + } + } + + return queryAdapterInstances; + } + + public List> getBeanPersistListeners() { + // add class registered BeanPersistController to the + // already created instances + for (Class cls : beanListenerList) { + try { + BeanPersistListener newInstance = (BeanPersistListener) cls.newInstance(); + persistListenerInstances.add(newInstance); + } catch (Exception e) { + String msg = "Error creating BeanPersistController " + cls; + logger.log(Level.SEVERE, msg, e); + } + } + + return persistListenerInstances; + } + + public List getBeanPersistControllers() { + // add class registered BeanPersistController to the + // already created instances + for (Class cls : beanControllerList) { + try { + BeanPersistController newInstance = (BeanPersistController) cls.newInstance(); + persistControllerInstances.add(newInstance); + } catch (Exception e) { + String msg = "Error creating BeanPersistController " + cls; + logger.log(Level.SEVERE, msg, e); + } + } + + return persistControllerInstances; + } + + public List getTransactionEventListeners() { + // add class registered TransactionEventListener to the + // already created instances + for (Class cls : transactionEventListenerList) { + try { + TransactionEventListener newInstance = (TransactionEventListener) cls.newInstance(); + transactionEventListenerInstances.add(newInstance); + } catch (Exception e) { + String msg = "Error creating TransactionEventListener " + cls; + logger.log(Level.SEVERE, msg, e); + } + } + + return transactionEventListenerInstances; + } + + /** + * Return the list of Embeddable classes. + */ + public ArrayList> getEmbeddables() { + return embeddableList; + } + + /** + * Return the list of entity classes. + */ + public ArrayList> getEntities() { + return entityList; + } + + /** + * Return the list of ScalarTypes found. + */ + public ArrayList> getScalarTypes() { + return scalarTypeList; + } + + /** + * Return the list of ScalarConverters found. + */ + public ArrayList> getScalarConverters() { + return scalarConverterList; + } + + /** + * Return the list of ScalarConverters found. + */ + public ArrayList> getCompoundTypes() { + return compoundTypeList; + } + + /** + * Return the list of BeanControllers found. + */ + public ArrayList> getBeanControllers() { + return beanControllerList; + } + + /** + * Return the list of TransactionEventListeners found + */ + public ArrayList> getTransactionEventListenerList() { + return transactionEventListenerList; + } + + /** + * Return the list of BeanFinders found. + */ + public ArrayList> getBeanFinders() { + return beanFinderList; + } + + /** + * Return the list of BeanListeners found. + */ + public ArrayList> getBeanListeners() { + return beanListenerList; + } + + /** + * Return the list of XML Beans. + */ + public ArrayList> getXmlBeanList() { + return xmlBeanList; + } + + public void add(Iterator> it) { + while (it.hasNext()) { + Class clazz = it.next(); + isMatch(clazz); + } + } + + public boolean isMatch(Class cls) { + + if (isEmbeddable(cls)) { + embeddableList.add(cls); + + } else if (isEntity(cls)) { + entityList.add(cls); + + } else if (isXmlBean(cls)){ + entityList.add(cls); + //xmlBeanList.add(cls); + + } else if (isInterestingInterface(cls)) { + return true; + + } else { + return false; + } + + return true; + } + + /** + * Look for interesting interfaces. + *

+ * This includes ScalarType, BeanController, BeanFinder and BeanListener. + *

+ */ + private boolean isInterestingInterface(Class cls) { + + boolean interesting = false; + + if (BeanPersistController.class.isAssignableFrom(cls)) { + beanControllerList.add(cls); + interesting = true; + } + + if (TransactionEventListener.class.isAssignableFrom(cls)) { + transactionEventListenerList.add(cls); + interesting = true; + } + + if (ScalarType.class.isAssignableFrom(cls)) { + scalarTypeList.add(cls); + interesting = true; + } + + if (ScalarTypeConverter.class.isAssignableFrom(cls)) { + scalarConverterList.add(cls); + interesting = true; + } + + if (CompoundType.class.isAssignableFrom(cls)) { + compoundTypeList.add(cls); + interesting = true; + } + + if (BeanFinder.class.isAssignableFrom(cls)) { + beanFinderList.add(cls); + interesting = true; + } + + if (BeanPersistListener.class.isAssignableFrom(cls)) { + beanListenerList.add(cls); + interesting = true; + } + + if (BeanQueryAdapter.class.isAssignableFrom(cls)) { + beanQueryAdapterList.add(cls); + interesting = true; + } + + if (ServerConfigStartup.class.isAssignableFrom(cls)){ + serverConfigStartupList.add(cls); + interesting = true; + } + + return interesting; + } + + private boolean isEntity(Class cls) { + + Annotation ann = cls.getAnnotation(Entity.class); + if (ann != null) { + return true; + } + ann = cls.getAnnotation(Table.class); + if (ann != null) { + return true; + } + ann = cls.getAnnotation(LdapDomain.class); + if (ann != null) { + return true; + } + return false; + } + + private boolean isEmbeddable(Class cls) { + + Annotation ann = cls.getAnnotation(Embeddable.class); + if (ann != null) { + return true; + } + return false; + } + + private boolean isXmlBean(Class cls) { + + Annotation ann = cls.getAnnotation(XmlRootElement.class); + if (ann != null) { + return true; + } + ann = cls.getAnnotation(XmlType.class); + if (ann != null) { + // Only looking for Beans and not Enums + return !cls.isEnum(); + } + return false; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/CacheOptions.java b/src/main/java/com/avaje/ebeaninternal/server/core/CacheOptions.java new file mode 100644 index 000000000..5a0b6b242 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/CacheOptions.java @@ -0,0 +1,90 @@ +package com.avaje.ebeaninternal.server.core; + +/** + * Options for controlling cache behaviour for a given type. + */ +public class CacheOptions { + + private boolean useCache; + + private boolean readOnly; + + private String naturalKey; + + private String warmingQuery; + + /** + * Construct with options. + */ + public CacheOptions() { + } + + /** + * Return true if this should use a cache for lazy loading. + */ + public boolean isUseCache() { + return useCache; + } + + /** + * Set whether to use the bean cache for the associated type. + */ + public void setUseCache(boolean useCache) { + this.useCache = useCache; + } + + /** + * Return the readOnly default setting. + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Set read Only default setting. + */ + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + /** + * Return the query used to warm the cache. + */ + public String getWarmingQuery() { + return warmingQuery; + } + + /** + * Set the cache warming query. + */ + public void setWarmingQuery(String warmingQuery) { + this.warmingQuery = warmingQuery; + } + + + /** + * Return true if a natural key is set. + */ + public boolean isUseNaturalKeyCache() { + return naturalKey != null; + } + + /** + * Return the natural key property. + */ + public String getNaturalKey() { + return naturalKey; + } + + /** + * Set the natural key property. + */ + public void setNaturalKey(String naturalKey) { + if (naturalKey == null || naturalKey.length() == 0){ + naturalKey = null; + } else { + this.naturalKey = naturalKey.trim(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/ConcurrencyMode.java b/src/main/java/com/avaje/ebeaninternal/server/core/ConcurrencyMode.java new file mode 100644 index 000000000..93e80dd65 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/ConcurrencyMode.java @@ -0,0 +1,22 @@ +package com.avaje.ebeaninternal.server.core; + +/** + * Optimistic concurrency mode used for updates and deletes. + */ +public enum ConcurrencyMode { + + /** + * No concurrency checking. + */ + NONE, + + /** + * Use a version column. + */ + VERSION, + + /** + * Use all the columns (except Lobs). + */ + ALL +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/ConfigBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/core/ConfigBuilder.java new file mode 100644 index 000000000..7c80db79d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/ConfigBuilder.java @@ -0,0 +1,25 @@ +package com.avaje.ebeaninternal.server.core; + +import com.avaje.ebean.config.ServerConfig; + + +/** + * Build a ServerConfig from ebean.properties. + */ +public class ConfigBuilder { + + /** + * Create a ServerConfig and load it from ebean.properties. + */ + public ServerConfig build(String serverName) { + + ServerConfig config = new ServerConfig(); + config.setName(serverName); + + config.loadFromProperties(); + + return config; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DatabasePlatformFactory.java b/src/main/java/com/avaje/ebeaninternal/server/core/DatabasePlatformFactory.java new file mode 100644 index 000000000..7023acf5e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DatabasePlatformFactory.java @@ -0,0 +1,191 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.H2Platform; +import com.avaje.ebean.config.dbplatform.HsqldbPlatform; +import com.avaje.ebean.config.dbplatform.MsSqlServer2000Platform; +import com.avaje.ebean.config.dbplatform.MsSqlServer2005Platform; +import com.avaje.ebean.config.dbplatform.MySqlPlatform; +import com.avaje.ebean.config.dbplatform.Oracle10Platform; +import com.avaje.ebean.config.dbplatform.Oracle9Platform; +import com.avaje.ebean.config.dbplatform.PostgresPlatform; +import com.avaje.ebean.config.dbplatform.SQLitePlatform; +import com.avaje.ebean.config.dbplatform.SqlAnywherePlatform; + +/** + * Create a DatabasePlatform from the configuration. + *

+ * Will used platform name or use the meta data from the JDBC driver to + * determine the platform automatically. + *

+ */ +public class DatabasePlatformFactory { + + private static final Logger logger = Logger.getLogger(DatabasePlatformFactory.class.getName()); + + /** + * Create the appropriate database specific platform. + */ + public DatabasePlatform create(ServerConfig serverConfig) { + + try { + + if (serverConfig.getDatabasePlatformName() != null) { + // choose based on dbName + return byDatabaseName(serverConfig.getDatabasePlatformName()); + + } + if (serverConfig.getDataSourceConfig().isOffline()) { + String m = "You must specify a DatabasePlatformName when you are offline"; + throw new PersistenceException(m); + } + // guess using meta data from driver + return byDataSource(serverConfig.getDataSource()); + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + /** + * Lookup the platform by name. + */ + private DatabasePlatform byDatabaseName(String dbName) throws SQLException { + + dbName = dbName.toLowerCase(); + if (dbName.equals("postgres83")) { + return new PostgresPlatform(); + } + if (dbName.equals("oracle9")) { + return new Oracle9Platform(); + } + if (dbName.equals("oracle10")) { + return new Oracle10Platform(); + } + if (dbName.equals("oracle")) { + return new Oracle10Platform(); + } + if (dbName.equals("sqlserver2005")) { + return new MsSqlServer2005Platform(); + } + if (dbName.equals("sqlserver2000")) { + return new MsSqlServer2000Platform(); + } + if (dbName.equals("sqlanywhere")) { + return new SqlAnywherePlatform(); + } + + if (dbName.equals("mysql")) { + return new MySqlPlatform(); + } + + if (dbName.equals("sqlite")) { + return new SQLitePlatform(); + } + + throw new RuntimeException("database platform " + dbName + " is not known?"); + } + + /** + * Use JDBC DatabaseMetaData to determine the platform. + */ + private DatabasePlatform byDataSource(DataSource dataSource) { + + Connection conn = null; + try { + conn = dataSource.getConnection(); + DatabaseMetaData metaData = conn.getMetaData(); + + return byDatabaseMeta(metaData); + + } catch (SQLException ex) { + throw new PersistenceException(ex); + + } finally { + try { + if (conn != null) { + conn.close(); + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + + /** + * Find the platform by the metaData.getDatabaseProductName(). + */ + private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData) throws SQLException { + + String dbProductName = metaData.getDatabaseProductName(); + dbProductName = dbProductName.toLowerCase(); + + int majorVersion = metaData.getDatabaseMajorVersion(); + + if (dbProductName.indexOf("oracle") > -1) { + if (majorVersion > 9) { + return new Oracle10Platform(); + } else { + return new Oracle9Platform(); + } + } + if (dbProductName.indexOf("microsoft") > -1) { + if (majorVersion > 8) { + return new MsSqlServer2005Platform(); + } else { + return new MsSqlServer2000Platform(); + } + } + + if (dbProductName.indexOf("mysql") > -1) { + return new MySqlPlatform(); + } + if (dbProductName.indexOf("h2") > -1) { + return new H2Platform(); + } + if (dbProductName.indexOf("hsql database engine") > -1) { + return new HsqldbPlatform(); + } + if (dbProductName.indexOf("postgres") > -1) { + return new PostgresPlatform(); + } + if (dbProductName.indexOf("sqlite") > -1) { + return new SQLitePlatform(); + } + if (dbProductName.indexOf("sql anywhere") > -1) { + return new SqlAnywherePlatform(); + } + + // use the standard one + return new DatabasePlatform(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DebugLazyLoad.java b/src/main/java/com/avaje/ebeaninternal/server/core/DebugLazyLoad.java new file mode 100644 index 000000000..d0d8db533 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DebugLazyLoad.java @@ -0,0 +1,111 @@ +package com.avaje.ebeaninternal.server.core; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.config.GlobalProperties; + +/** + * Helper used in producing debug output on lazy loading. + */ +public class DebugLazyLoad { + + private final String[] ignoreList; + + private final boolean debug; + + public DebugLazyLoad(boolean lazyLoadDebug) { + ignoreList = buildLazyLoadIgnoreList(); + debug = lazyLoadDebug; + } + + /** + * Return true if debugging is on. + */ + public boolean isDebug() { + return debug; + } + + /** + * Return the StackTraceElement that is believed to be the line of code that + * triggered the lazy loading. + *

+ * This is determined by going up the stack trace ignoring all the sections + * of java and Ebean code etc until you find code not in the ignore list - + * this is assumed to be your application code that triggered the lazy + * loading (it could be a third party layer such as a web template). + *

+ */ + public StackTraceElement getStackTraceElement(Class beanType) { + + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (int i = 0; i < stackTrace.length; i++) { + if (isStackLine(stackTrace[i], beanType)) { + return stackTrace[i]; + } + } + return null; + } + + /** + * Return true if the code is expected to be application code. + */ + private boolean isStackLine(StackTraceElement element, Class beanType) { + + String stackClass = element.getClassName(); + + if (isBeanClass(beanType, stackClass)) { + return false; + } + + for (int i = 0; i < ignoreList.length; i++) { + if (stackClass.startsWith(ignoreList[i])) { + return false; + } + } + return true; + } + + /** + * Recurse up the inheritance checking to see if the stackClass is + * this beanType (or a parent type). + */ + private boolean isBeanClass(Class beanType, String stackClass) { + if (stackClass.startsWith(beanType.getName())) { + return true; + } + Class superCls = beanType.getSuperclass(); + if (superCls.equals(Object.class)){ + return false; + } else { + return isBeanClass(superCls, stackClass); + } + } + + + /** + * Build list of prefixes used to find the application code that triggered + * the lazy loading. + */ + private String[] buildLazyLoadIgnoreList() { + + List ignore = new ArrayList(); + + // code that should be ignored when searching + // the stack trace elements + ignore.add("com.avaje.ebean"); + ignore.add("java"); + ignore.add("sun.reflect"); + ignore.add("org.codehaus.groovy.runtime."); + + String extraIgnore = GlobalProperties.get("debug.lazyload.ignore", null); + if (extraIgnore != null) { + String[] split = extraIgnore.split(","); + for (int i = 0; i < split.length; i++) { + ignore.add(split[i].trim()); + } + } + + return ignore.toArray(new String[ignore.size()]); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBackgroundExecutor.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBackgroundExecutor.java new file mode 100644 index 000000000..026ae1735 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBackgroundExecutor.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.concurrent.TimeUnit; + +import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; +import com.avaje.ebeaninternal.server.lib.DaemonScheduleThreadPool; +import com.avaje.ebeaninternal.server.lib.DaemonThreadPool; + +/** + * The default implementation of the BackgroundExecutor. + * + * @author rbygrave + */ +public class DefaultBackgroundExecutor implements SpiBackgroundExecutor { + + private final DaemonThreadPool pool; + + private final DaemonScheduleThreadPool schedulePool; + + /** + * Construct the default implementation of BackgroundExecutor. + * + * @param mainPoolSize + * the core size of the thread pool. + * @param keepAliveSecs + * the time in seconds idle threads are keep alive + * @param shutdownWaitSeconds + * the time in seconds allowed for the pool to shutdown nicely. + * After this the pool is forced to shutdown. + */ + public DefaultBackgroundExecutor(int mainPoolSize, int schedulePoolSize, long keepAliveSecs,int shutdownWaitSeconds, String namePrefix) { + this.pool = new DaemonThreadPool(mainPoolSize, keepAliveSecs, shutdownWaitSeconds, namePrefix); + this.schedulePool = new DaemonScheduleThreadPool(schedulePoolSize, shutdownWaitSeconds, namePrefix+"-periodic-"); + } + + /** + * Execute a Runnable using a background thread. + */ + public void execute(Runnable r) { + pool.execute(r); + } + + public void executePeriodically(Runnable r, long delay, TimeUnit unit) { + schedulePool.scheduleWithFixedDelay(r, delay, delay, unit); + } + + public void shutdown() { + pool.shutdown(); + schedulePool.shutdown(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java new file mode 100644 index 000000000..2a3c7ae2b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java @@ -0,0 +1,478 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.EntityNotFoundException; + +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.LoadBeanContext; +import com.avaje.ebeaninternal.api.LoadBeanRequest; +import com.avaje.ebeaninternal.api.LoadManyContext; +import com.avaje.ebeaninternal.api.LoadManyRequest; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; + +/** + * Helper to handle lazy loading and refreshing of beans. + * + * @author rbygrave + */ +public class DefaultBeanLoader { + + private static final Logger logger = Logger.getLogger(DefaultBeanLoader.class.getName()); + + private final DebugLazyLoad debugLazyLoad; + + private final DefaultServer server; + + protected DefaultBeanLoader(DefaultServer server, DebugLazyLoad debugLazyLoad) { + this.server = server; + this.debugLazyLoad = debugLazyLoad; + } + + /** + * Return a batch size that might be less than the requestedBatchSize. + *

+ * This means we can have large and variable requestedBatchSizes. + *

+ *

+ * We want to restrict the number of different batch sizes as we want to + * re-use the query plan cache and get DB statement re-use. + *

+ */ + private int getBatchSize(int batchListSize, int requestedBatchSize) { + if (batchListSize == requestedBatchSize) { + return batchListSize; + } + if (batchListSize == 1) { + // there is only one bean/collection to load + return 1; + } + if (requestedBatchSize <= 5) { + // anything less than 5 becomes 5 + return 5; + } + if (batchListSize <= 10 || requestedBatchSize <= 10) { + // 10 or less to load + // ... or we wanted a batch size between 6 and 10 + return 10; + } + if (batchListSize <= 20 || requestedBatchSize <= 20) { + // 20 or less to load + // ... or we wanted a batch size between 11 and 20 + return 20; + } + if (batchListSize <= 50) { + return 50; + } + return requestedBatchSize; + } + + public void refreshMany(Object parentBean, String propertyName) { + refreshMany(parentBean, propertyName, null); + } + + public void loadMany(LoadManyRequest loadRequest) { + + List> batch = loadRequest.getBatch(); + + int batchSize = getBatchSize(batch.size(), loadRequest.getBatchSize()); + + LoadManyContext ctx = loadRequest.getLoadContext(); + BeanPropertyAssocMany many = ctx.getBeanProperty(); + + PersistenceContext pc = ctx.getPersistenceContext(); + + ArrayList idList = new ArrayList(batchSize); + + for (int i = 0; i < batch.size(); i++) { + BeanCollection bc = batch.get(i); + Object ownerBean = bc.getOwnerBean(); + Object id = many.getParentId(ownerBean); + idList.add(id); + } + int extraIds = batchSize - batch.size(); + if (extraIds > 0) { + Object firstId = idList.get(0); + for (int i = 0; i < extraIds; i++) { + idList.add(firstId); + } + } + + BeanDescriptor desc = ctx.getBeanDescriptor(); + + String idProperty = desc.getIdBinder().getIdProperty(); + + SpiQuery query = (SpiQuery) server.createQuery(desc.getBeanType()); + query.setMode(Mode.LAZYLOAD_MANY); + query.setLazyLoadManyPath(many.getName()); + query.setPersistenceContext(pc); + query.select(idProperty); + query.fetch(many.getName()); + + if (idList.size() == 1) { + query.where().idEq(idList.get(0)); + } else { + query.where().idIn(idList); + } + + String mode = loadRequest.isLazy() ? "+lazy" : "+query"; + query.setLoadDescription(mode, loadRequest.getDescription()); + + // potentially changes the joins and selected properties + ctx.configureQuery(query); + + if (loadRequest.isOnlyIds()) { + // override to just select the Id values + query.fetch(many.getName(), many.getTargetIdProperty()); + } + + server.findList(query, loadRequest.getTransaction()); + + // check for BeanCollection's that where never processed + // in the +query or +lazy load due to no rows (predicates) + for (int i = 0; i < batch.size(); i++) { + BeanCollection bc = batch.get(i); + if (bc.checkEmptyLazyLoad()) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("BeanCollection after load was empty. Owner:" + batch.get(i).getOwnerBean()); + } + } else if (loadRequest.isLoadCache()) { + Object parentId = desc.getId(bc.getOwnerBean()); + desc.cachePutMany(many, bc, parentId); + } + } + } + + public void loadMany(BeanCollection bc, LoadManyContext ctx, boolean onlyIds) { + + Object parentBean = bc.getOwnerBean(); + String propertyName = bc.getPropertyName(); + + ObjectGraphNode node = ctx == null ? null : ctx.getObjectGraphNode(); + + loadManyInternal(parentBean, propertyName, null, false, node, onlyIds); + + if (server.getAdminLogging().isDebugLazyLoad()) { + + Class cls = parentBean.getClass(); + BeanDescriptor desc = server.getBeanDescriptor(cls); + BeanPropertyAssocMany many = (BeanPropertyAssocMany) desc.getBeanProperty(propertyName); + + StackTraceElement cause = debugLazyLoad.getStackTraceElement(cls); + + String msg = "debug.lazyLoad " + many.getManyType() + " [" + desc + "][" + propertyName + "]"; + if (cause != null) { + msg += " at: " + cause; + } + System.err.println(msg); + } + } + + public void refreshMany(Object parentBean, String propertyName, Transaction t) { + loadManyInternal(parentBean, propertyName, t, true, null, false); + } + + private void loadManyInternal(Object parentBean, String propertyName, Transaction t, boolean refresh, ObjectGraphNode node, boolean onlyIds) { + + boolean vanilla = (parentBean instanceof EntityBean == false); + + EntityBeanIntercept ebi = null; + PersistenceContext pc = null; + BeanCollection beanCollection = null; + ExpressionList filterMany = null; + + if (!vanilla) { + ebi = ((EntityBean) parentBean)._ebean_getIntercept(); + pc = ebi.getPersistenceContext(); + } + + BeanDescriptor parentDesc = server.getBeanDescriptor(parentBean.getClass()); + BeanPropertyAssocMany many = (BeanPropertyAssocMany) parentDesc.getBeanProperty(propertyName); + + Object currentValue = many.getValueUnderlying(parentBean); + if (currentValue instanceof BeanCollection) { + beanCollection = (BeanCollection) currentValue; + filterMany = beanCollection.getFilterMany(); + } + + Object parentId = parentDesc.getId(parentBean); + + if (pc == null) { + pc = new DefaultPersistenceContext(); + pc.put(parentId, parentBean); + } + + boolean useManyIdCache = !vanilla && beanCollection != null && parentDesc.cacheIsUseManyId(); + if (useManyIdCache) { + Boolean readOnly = null; + if (ebi != null && ebi.isReadOnly()) { + readOnly = Boolean.TRUE; + } + if (parentDesc.cacheLoadMany(many, beanCollection, parentId, readOnly, false)) { + return; + } + } + + SpiQuery query = (SpiQuery) server.createQuery(parentDesc.getBeanType()); + + if (refresh) { + // populate a new collection + Object emptyCollection = many.createEmpty(vanilla); + many.setValue(parentBean, emptyCollection); + query.setLoadDescription("+refresh", null); + } else { + query.setLoadDescription("+lazy", null); + } + + if (node != null) { + // so we can hook back to the root query + query.setParentNode(node); + } + + String idProperty = parentDesc.getIdBinder().getIdProperty(); + query.select(idProperty); + + if (onlyIds) { + query.fetch(many.getName(), many.getTargetIdProperty()); + } else { + query.fetch(many.getName()); + } + if (filterMany != null) { + query.setFilterMany(many.getName(), filterMany); + } + + query.where().idEq(parentId); + query.setUseCache(false); + query.setMode(Mode.LAZYLOAD_MANY); + query.setLazyLoadManyPath(many.getName()); + query.setPersistenceContext(pc); + query.setVanillaMode(vanilla); + + if (ebi != null) { + if (ebi.isReadOnly()) { + query.setReadOnly(true); + } + } + + server.findUnique(query, t); + + if (beanCollection != null) { + if (beanCollection.checkEmptyLazyLoad()) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("BeanCollection after load was empty. Owner:" + beanCollection.getOwnerBean()); + } + } else if (useManyIdCache) { + parentDesc.cachePutMany(many, beanCollection, parentId); + } + } + } + + + /** + * Load a batch of beans for +query or +lazy loading. + */ + public void loadBean(LoadBeanRequest loadRequest) { + + List batch = loadRequest.getBatch(); + + if (batch.isEmpty()) { + throw new RuntimeException("Nothing in batch?"); + } + + int batchSize = getBatchSize(batch.size(), loadRequest.getBatchSize()); + + LoadBeanContext ctx = loadRequest.getLoadContext(); + BeanDescriptor desc = ctx.getBeanDescriptor(); + + Class beanType = desc.getBeanType(); + + EntityBeanIntercept[] ebis = batch.toArray(new EntityBeanIntercept[batch.size()]); + ArrayList idList = new ArrayList(batchSize); + + for (int i = 0; i < batch.size(); i++) { + EntityBeanIntercept ebi = batch.get(i); + Object bean = ebi.getOwner(); + Object id = desc.getId(bean); + idList.add(id); + } + + if (idList.isEmpty()) { + // everything was loaded from cache + return; + } + + int extraIds = batchSize - batch.size(); + if (extraIds > 0) { + // for performance make up the Id's to the batch size + // so we get the same query (for Ebean and the db) + Object firstId = idList.get(0); + for (int i = 0; i < extraIds; i++) { + // just add the first Id again + idList.add(firstId); + } + } + + PersistenceContext persistenceContext = ctx.getPersistenceContext(); + + // query the database + for (int i = 0; i < ebis.length; i++) { + Object parentBean = ebis[i].getParentBean(); + if (parentBean != null) { + // Special case for OneToOne + BeanDescriptor parentDesc = server.getBeanDescriptor(parentBean.getClass()); + Object parentId = parentDesc.getId(parentBean); + persistenceContext.put(parentId, parentBean); + } + } + + SpiQuery query = (SpiQuery) server.createQuery(beanType); + + query.setMode(Mode.LAZYLOAD_BEAN); + query.setPersistenceContext(persistenceContext); + + String mode = loadRequest.isLazy() ? "+lazy" : "+query"; + query.setLoadDescription(mode, loadRequest.getDescription()); + + ctx.configureQuery(query, loadRequest.getLazyLoadProperty()); + + // make sure the query doesn't use the cache + // query.setUseCache(false); + if (idList.size() == 1) { + query.where().idEq(idList.get(0)); + } else { + query.where().idIn(idList); + } + + List list = server.findList(query, loadRequest.getTransaction()); + + if (loadRequest.isLoadCache()) { + for (int i = 0; i < list.size(); i++) { + desc.cachePutBeanData(list.get(i)); + } + } + + for (int i = 0; i < ebis.length; i++) { + if (ebis[i].isReference()) { + // The underlying row in DB was deleted. Mark this bean as 'failed' + // but allow processing to continue until it is accessed by client code + ebis[i].setLazyLoadFailure(); + } + } + + } + + public void refresh(Object bean) { + refreshBeanInternal(bean, SpiQuery.Mode.REFRESH_BEAN); + } + + public void loadBean(EntityBeanIntercept ebi) { + refreshBeanInternal(ebi.getOwner(), SpiQuery.Mode.LAZYLOAD_BEAN); + } + + private void refreshBeanInternal(Object bean, SpiQuery.Mode mode) { + + boolean vanilla = (bean instanceof EntityBean == false); + + EntityBeanIntercept ebi = null; + PersistenceContext pc = null; + + if (!vanilla) { + ebi = ((EntityBean) bean)._ebean_getIntercept(); + pc = ebi.getPersistenceContext(); + } + + BeanDescriptor desc = server.getBeanDescriptor(bean.getClass()); + Object id = desc.getId(bean); + + if (pc == null) { + // a reference with no existing persistenceContext + pc = new DefaultPersistenceContext(); + pc.put(id, bean); + if (ebi != null) { + ebi.setPersistenceContext(pc); + } + } + + if (ebi != null) { + if (SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) { + // lazy loading and the bean cache is active + if (desc.loadFromCache(bean, ebi, id)) { + return; + } + } + if (desc.lazyLoadMany(ebi)) { + return; + } + } + + SpiQuery query = (SpiQuery) server.createQuery(desc.getBeanType()); + if (ebi != null) { + Object parentBean = ebi.getParentBean(); + if (parentBean != null) { + // Special case for OneToOne + BeanDescriptor parentDesc = server.getBeanDescriptor(parentBean.getClass()); + Object parentId = parentDesc.getId(parentBean); + pc.putIfAbsent(parentId, parentBean); + } + + query.setLazyLoadProperty(ebi.getLazyLoadProperty()); + } + + // don't collect autoFetch usage profiling information + // as we just copy the data out of these fetched beans + // and put the data into the original bean + query.setUsageProfiling(false); + query.setPersistenceContext(pc); + + query.setMode(mode); + query.setId(id); + // make sure the query doesn't use the cache + if (mode.equals(SpiQuery.Mode.REFRESH_BEAN)) { + query.setUseCache(false); + } + query.setVanillaMode(vanilla); + + if (ebi != null && ebi.isReadOnly()) { + query.setReadOnly(true); + } + + Object dbBean = query.findUnique(); + if (dbBean == null) { + String msg = "Bean not found during lazy load or refresh." + " id[" + id + "] type[" + desc.getBeanType() + "]"; + throw new EntityNotFoundException(msg); + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanState.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanState.java new file mode 100644 index 000000000..b0980925d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanState.java @@ -0,0 +1,77 @@ +package com.avaje.ebeaninternal.server.core; + +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.Set; + +import com.avaje.ebean.BeanState; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; + +/** + * Default implementation of BeanState. + */ +public class DefaultBeanState implements BeanState { + + final EntityBean entityBean; + + final EntityBeanIntercept intercept; + + public DefaultBeanState(EntityBean entityBean){ + this.entityBean = entityBean; + this.intercept = entityBean._ebean_getIntercept(); + } + + public boolean isReference() { + return intercept.isReference(); + } + + public boolean isNew() { + return intercept.isNew(); + } + + public boolean isNewOrDirty() { + return intercept.isNewOrDirty(); + } + + public boolean isDirty() { + return intercept.isDirty(); + } + + public Set getLoadedProps() { + Set props = intercept.getLoadedProps(); + return props == null ? null : Collections.unmodifiableSet(props); + } + + public Set getChangedProps() { + Set props = intercept.getChangedProps(); + return props == null ? null : Collections.unmodifiableSet(props); + } + + public boolean isReadOnly() { + return intercept.isReadOnly(); + } + + public void setReadOnly(boolean readOnly){ + intercept.setReadOnly(readOnly); + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + entityBean.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + entityBean.removePropertyChangeListener(listener); + } + + public void setLoaded(Set loadedProperties) { + intercept.setLoadedProps(loadedProperties); + intercept.setLoaded(); + } + + public void setReference() { + intercept.setReference(); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultCallableSql.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultCallableSql.java new file mode 100644 index 000000000..389e901b7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultCallableSql.java @@ -0,0 +1,143 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.io.Serializable; +import java.sql.CallableStatement; +import java.sql.SQLException; + +import com.avaje.ebean.CallableSql; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiCallableSql; +import com.avaje.ebeaninternal.api.TransactionEventTable; +import com.avaje.ebeaninternal.api.BindParams.Param; + + +public class DefaultCallableSql implements Serializable, SpiCallableSql { + + private static final long serialVersionUID = 8984272253185424701L; + + private transient final EbeanServer server; + + /** + * The callable sql. + */ + private String sql; + + /** + * To display in the transaction log to help identify the procedure. + */ + private String label; + + private int timeout; + + /** + * Holds the table modification information. On commit this information is + * used to manage the cache etc. + */ + private TransactionEventTable transactionEvent = new TransactionEventTable(); + + private BindParams bindParameters = new BindParams(); + + /** + * Create with callable sql. + */ + public DefaultCallableSql(EbeanServer server, String sql) { + this.server = server; + this.sql = sql; + } + + public void execute() { + server.execute(this, null); + } + + public String getLabel() { + return label; + } + + public CallableSql setLabel(String label) { + this.label = label; + return this; + } + + public int getTimeout() { + return timeout; + } + + public String getSql() { + return sql; + } + + public CallableSql setTimeout(int secs) { + this.timeout = secs; + return this; + } + + public CallableSql setSql(String sql) { + this.sql = sql; + return this; + } + + public CallableSql bind(int position, Object value) { + bindParameters.setParameter(position, value); + return this; + } + + public CallableSql setParameter(int position, Object value) { + bindParameters.setParameter(position, value); + return this; + } + + public CallableSql registerOut(int position, int type) { + bindParameters.registerOut(position, type); + return this; + } + + public Object getObject(int position) { + Param p = bindParameters.getParameter(position); + return p.getOutValue(); + } + + public boolean executeOverride(CallableStatement cstmt) throws SQLException { + return false; + } + + public CallableSql addModification(String tableName, boolean inserts, boolean updates, + boolean deletes) { + + transactionEvent.add(tableName, inserts, updates, deletes); + return this; + } + + /** + * Return the TransactionEvent which holds the table modification + * information for this CallableSql. This information is merged into the + * transaction after the transaction is commited. + */ + public TransactionEventTable getTransactionEventTable() { + return transactionEvent; + } + + public BindParams getBindParams() { + return bindParameters; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java new file mode 100644 index 000000000..c47f7be08 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -0,0 +1,2131 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.FutureTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.AdminAutofetch; +import com.avaje.ebean.AdminLogging; +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.BeanState; +import com.avaje.ebean.CallableSql; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.Filter; +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.FutureList; +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.PagingList; +import com.avaje.ebean.Query; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryResultVisitor; +import com.avaje.ebean.SqlFutureList; +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.TxCallable; +import com.avaje.ebean.TxIsolation; +import com.avaje.ebean.TxRunnable; +import com.avaje.ebean.TxScope; +import com.avaje.ebean.TxType; +import com.avaje.ebean.Update; +import com.avaje.ebean.ValuePair; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.CallStack; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.config.EncryptKeyManager; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.ldap.LdapConfig; +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.text.csv.CsvReader; +import com.avaje.ebean.text.json.JsonContext; +import com.avaje.ebean.text.json.JsonElement; +import com.avaje.ebeaninternal.api.LoadBeanRequest; +import com.avaje.ebeaninternal.api.LoadManyRequest; +import com.avaje.ebeaninternal.api.ScopeTrans; +import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.api.SpiQuery.Type; +import com.avaje.ebeaninternal.api.SpiSqlQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.TransactionEventTable; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.ddl.DdlGenerator; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeaninternal.server.deploy.BeanManager; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DNativeQuery; +import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; +import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.el.ElFilter; +import com.avaje.ebeaninternal.server.jmx.MAdminAutofetch; +import com.avaje.ebeaninternal.server.ldap.DefaultLdapOrmQuery; +import com.avaje.ebeaninternal.server.ldap.LdapOrmQueryEngine; +import com.avaje.ebeaninternal.server.ldap.LdapOrmQueryRequest; +import com.avaje.ebeaninternal.server.ldap.expression.LdapExpressionFactory; +import com.avaje.ebeaninternal.server.lib.ShutdownManager; +import com.avaje.ebeaninternal.server.loadcontext.DLoadContext; +import com.avaje.ebeaninternal.server.query.CQuery; +import com.avaje.ebeaninternal.server.query.CQueryEngine; +import com.avaje.ebeaninternal.server.query.CallableQueryIds; +import com.avaje.ebeaninternal.server.query.CallableQueryList; +import com.avaje.ebeaninternal.server.query.CallableQueryRowCount; +import com.avaje.ebeaninternal.server.query.CallableSqlQueryList; +import com.avaje.ebeaninternal.server.query.LimitOffsetPagingQuery; +import com.avaje.ebeaninternal.server.query.QueryFutureIds; +import com.avaje.ebeaninternal.server.query.QueryFutureList; +import com.avaje.ebeaninternal.server.query.QueryFutureRowCount; +import com.avaje.ebeaninternal.server.query.SqlQueryFutureList; +import com.avaje.ebeaninternal.server.querydefn.DefaultOrmQuery; +import com.avaje.ebeaninternal.server.querydefn.DefaultOrmUpdate; +import com.avaje.ebeaninternal.server.querydefn.DefaultRelationalQuery; +import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam; +import com.avaje.ebeaninternal.server.text.csv.TCsvReader; +import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; +import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; +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; + +/** + * The default server side implementation of EbeanServer. + */ +public final class DefaultServer implements SpiEbeanServer { + + private static final Logger logger = Logger.getLogger(DefaultServer.class.getName()); + + /** + * Used when no errors are found validating a property. + */ + private static final InvalidValue[] EMPTY_INVALID_VALUES = new InvalidValue[0]; + + private final String serverName; + + private final DatabasePlatform databasePlatform; + + private final AdminLogging adminLogging; + + private final AdminAutofetch adminAutofetch; + + private final TransactionManager transactionManager; + + private final TransactionScopeManager transactionScopeManager; + + private final int maxCallStack; + + /** + * Ebean defaults this to true but for EJB compatible behaviour set this to + * false; + */ + private final boolean rollbackOnChecked; + private final boolean defaultDeleteMissingChildren; + private final boolean defaultUpdateNullProperties; + + /** + * Set to true if vanilla objects should be returned by default from queries + * (with dynamic subclassing). + */ + private final boolean vanillaMode; + private final boolean vanillaRefMode; + + private final LdapOrmQueryEngine ldapQueryEngine; + + /** + * Handles the save, delete, updateSql CallableSql. + */ + private final Persister persister; + + private final OrmQueryEngine queryEngine; + + private final RelationalQueryEngine relationalQueryEngine; + + private final ServerCacheManager serverCacheManager; + + private final BeanDescriptorManager beanDescriptorManager; + + private final DiffHelp diffHelp = new DiffHelp(); + + private final AutoFetchManager autoFetchManager; + + private final CQueryEngine cqueryEngine; + + private final DdlGenerator ddlGenerator; + + private final ExpressionFactory ldapExpressionFactory = new LdapExpressionFactory(); + + private final ExpressionFactory expressionFactory; + + private final SpiBackgroundExecutor backgroundExecutor; + + private final DefaultBeanLoader beanLoader; + + private final EncryptKeyManager encryptKeyManager; + + private final JsonContext jsonContext; + + /** + * The MBean name used to register Ebean. + */ + private String mbeanName; + + /** + * The MBeanServer Ebean is registered with. + */ + private MBeanServer mbeanServer; + + /** + * The default batch size for lazy loading beans or collections. + */ + private int lazyLoadBatchSize; + + /** The query batch size */ + private int queryBatchSize; + /** + * JDBC driver specific handling for JDBC batch execution. + */ + private PstmtBatch pstmtBatch; + + /** + * Create the DefaultServer. + */ + public DefaultServer(InternalConfiguration config, ServerCacheManager cache) { + + this.vanillaMode = config.getServerConfig().isVanillaMode(); + this.vanillaRefMode = config.getServerConfig().isVanillaRefMode(); + + this.serverCacheManager = cache; + this.pstmtBatch = config.getPstmtBatch(); + this.databasePlatform = config.getDatabasePlatform(); + this.backgroundExecutor = config.getBackgroundExecutor(); + this.serverName = config.getServerConfig().getName(); + this.lazyLoadBatchSize = config.getServerConfig().getLazyLoadBatchSize(); + this.queryBatchSize = config.getServerConfig().getQueryBatchSize(); + this.cqueryEngine = config.getCQueryEngine(); + this.expressionFactory = config.getExpressionFactory(); + this.adminLogging = config.getLogControl(); + this.encryptKeyManager = config.getServerConfig().getEncryptKeyManager(); + + this.beanDescriptorManager = config.getBeanDescriptorManager(); + beanDescriptorManager.setEbeanServer(this); + + this.maxCallStack = GlobalProperties.getInt("ebean.maxCallStack", 5); + + this.defaultUpdateNullProperties = "true" + .equalsIgnoreCase(config.getServerConfig().getProperty("defaultUpdateNullProperties", "false")); + this.defaultDeleteMissingChildren = "true".equalsIgnoreCase(config.getServerConfig() + .getProperty("defaultDeleteMissingChildren", "true")); + + this.rollbackOnChecked = GlobalProperties.getBoolean("ebean.transaction.rollbackOnChecked", true); + this.transactionManager = config.getTransactionManager(); + this.transactionScopeManager = config.getTransactionScopeManager(); + + this.persister = config.createPersister(this); + this.queryEngine = config.createOrmQueryEngine(); + this.relationalQueryEngine = config.createRelationalQueryEngine(); + + this.autoFetchManager = config.createAutoFetchManager(this); + this.adminAutofetch = new MAdminAutofetch(autoFetchManager); + + this.ddlGenerator = new DdlGenerator(this, config.getDatabasePlatform(), config.getServerConfig()); + this.beanLoader = new DefaultBeanLoader(this, config.getDebugLazyLoad()); + this.jsonContext = config.createJsonContext(this); + + LdapConfig ldapConfig = config.getServerConfig().getLdapConfig(); + if (ldapConfig == null) { + this.ldapQueryEngine = null; + } else { + this.ldapQueryEngine = new LdapOrmQueryEngine(ldapConfig.isVanillaMode(), ldapConfig.getContextFactory()); + } + + ShutdownManager.register(new Shutdown()); + } + + public boolean isDefaultDeleteMissingChildren() { + return defaultDeleteMissingChildren; + } + + public boolean isDefaultUpdateNullProperties() { + return defaultUpdateNullProperties; + } + + public boolean isVanillaMode() { + return vanillaMode; + } + + public int getLazyLoadBatchSize() { + return lazyLoadBatchSize; + } + + public PstmtBatch getPstmtBatch() { + return pstmtBatch; + } + + public DatabasePlatform getDatabasePlatform() { + return databasePlatform; + } + + public BackgroundExecutor getBackgroundExecutor() { + return backgroundExecutor; + } + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } + + public DdlGenerator getDdlGenerator() { + return ddlGenerator; + } + + public AdminLogging getAdminLogging() { + return adminLogging; + } + + public AdminAutofetch getAdminAutofetch() { + return adminAutofetch; + } + + public AutoFetchManager getAutoFetchManager() { + return autoFetchManager; + } + + /** + * Run any initialisation required before registering with the ClusterManager. + */ + public void initialise() { + if (encryptKeyManager != null) { + encryptKeyManager.initialise(); + } + List> list = beanDescriptorManager.getBeanDescriptorList(); + for (int i = 0; i < list.size(); i++) { + list.get(i).cacheInitialise(); + } + + } + + /** + * Start any services after registering with the ClusterManager. + */ + public void start() { + } + + public void registerMBeans(MBeanServer mbeanServer, int uniqueServerId) { + + this.mbeanServer = mbeanServer; + this.mbeanName = "Ebean:server=" + serverName + uniqueServerId; + + ObjectName adminName; + ObjectName autofethcName; + try { + adminName = new ObjectName(mbeanName + ",function=Logging"); + autofethcName = new ObjectName(mbeanName + ",key=AutoFetch"); + } catch (Exception e) { + String msg = "Failed to register the JMX beans for Ebean server [" + serverName + "]."; + logger.log(Level.SEVERE, msg, e); + return; + } + + try { + mbeanServer.registerMBean(adminLogging, adminName); + mbeanServer.registerMBean(adminAutofetch, autofethcName); + + } catch (InstanceAlreadyExistsException e) { + // tomcat webapp reloading + String msg = "JMX beans for Ebean server [" + serverName + "] already registered. Will try unregister/register" + e.getMessage(); + logger.log(Level.WARNING, msg); + try { + mbeanServer.unregisterMBean(adminName); + mbeanServer.unregisterMBean(autofethcName); + // re-register + mbeanServer.registerMBean(adminLogging, adminName); + mbeanServer.registerMBean(adminAutofetch, autofethcName); + + } catch (Exception ae) { + String amsg = "Unable to unregister/register the JMX beans for Ebean server [" + serverName + "]."; + logger.log(Level.SEVERE, amsg, ae); + } + } catch (Exception e) { + String msg = "Error registering MBean[" + mbeanName + "]"; + logger.log(Level.SEVERE, msg, e); + } + } + + private final class Shutdown implements Runnable { + public void run() { + try { + if (mbeanServer != null) { + mbeanServer.unregisterMBean(new ObjectName(mbeanName + ",function=Logging")); + mbeanServer.unregisterMBean(new ObjectName(mbeanName + ",key=AutoFetch")); + } + } catch (Exception e) { + String msg = "Error unregistering Ebean " + mbeanName; + logger.log(Level.SEVERE, msg, e); + } + + // shutdown services + transactionManager.shutdown(); + autoFetchManager.shutdown(); + backgroundExecutor.shutdown(); + } + } + + /** + * Return the server name. + */ + public String getName() { + return serverName; + } + + public BeanState getBeanState(Object bean) { + if (bean instanceof EntityBean) { + return new DefaultBeanState((EntityBean) bean); + } + // if using "subclassing" (not enhancement) this will + // return null for 'vanilla' instances (not subclassed) + return null; + } + + /** + * Run the cache warming queries on all beans that have them defined. + */ + public void runCacheWarming() { + List> descList = beanDescriptorManager.getBeanDescriptorList(); + for (int i = 0; i < descList.size(); i++) { + descList.get(i).runCacheWarming(); + } + } + + public void runCacheWarming(Class beanType) { + BeanDescriptor desc = beanDescriptorManager.getBeanDescriptor(beanType); + if (desc == null) { + String msg = "Is " + beanType + " an entity? Could not find a BeanDescriptor"; + throw new PersistenceException(msg); + } else { + desc.runCacheWarming(); + } + } + + /** + * Compile a query. Only valid for ORM queries (not LDAP) + */ + public CQuery compileQuery(Query query, Transaction t) { + SpiOrmQueryRequest qr = createQueryRequest(Type.SUBQUERY, query, t); + OrmQueryRequest orm = (OrmQueryRequest) qr; + return cqueryEngine.buildQuery(orm); + } + + public CQueryEngine getQueryEngine() { + return cqueryEngine; + } + + public ServerCacheManager getServerCacheManager() { + return serverCacheManager; + } + + /** + * Return the Profile Listener. + */ + public AutoFetchManager getProfileListener() { + return autoFetchManager; + } + + /** + * Return the Relational query engine. + */ + public RelationalQueryEngine getRelationalQueryEngine() { + return relationalQueryEngine; + } + + public void refreshMany(Object parentBean, String propertyName, Transaction t) { + + beanLoader.refreshMany(parentBean, propertyName, t); + } + + public void refreshMany(Object parentBean, String propertyName) { + + beanLoader.refreshMany(parentBean, propertyName); + } + + public void loadMany(LoadManyRequest loadRequest) { + + beanLoader.loadMany(loadRequest); + } + + public void loadMany(BeanCollection bc, boolean onlyIds) { + + beanLoader.loadMany(bc, null, onlyIds); + } + + public void refresh(Object bean) { + + beanLoader.refresh(bean); + } + + public void loadBean(LoadBeanRequest loadRequest) { + + beanLoader.loadBean(loadRequest); + } + + public void loadBean(EntityBeanIntercept ebi) { + + beanLoader.loadBean(ebi); + } + + public InvalidValue validate(Object bean) { + if (bean == null) { + return null; + } + BeanDescriptor beanDescriptor = getBeanDescriptor(bean.getClass()); + return beanDescriptor.validate(true, bean); + } + + public InvalidValue[] validate(Object bean, String propertyName, Object value) { + if (bean == null) { + return null; + } + BeanDescriptor beanDescriptor = getBeanDescriptor(bean.getClass()); + BeanProperty prop = beanDescriptor.getBeanProperty(propertyName); + if (prop == null) { + String msg = "property " + propertyName + " was not found?"; + throw new PersistenceException(msg); + } + if (value == null) { + value = prop.getValue(bean); + } + List errors = prop.validate(true, value); + if (errors == null) { + return EMPTY_INVALID_VALUES; + } else { + return InvalidValue.toArray(errors); + } + } + + public Map diff(Object a, Object b) { + if (a == null) { + return null; + } + + BeanDescriptor desc = getBeanDescriptor(a.getClass()); + return diffHelp.diff(a, b, desc); + } + + /** + * Process committed beans from another framework or server in another + * cluster. + *

+ * This notifies this instance of the framework that beans have been committed + * externally to it. Either by another framework or clustered server. It needs + * to maintain its cache and text indexes appropriately. + *

+ */ + public void externalModification(TransactionEventTable tableEvent) { + SpiTransaction t = transactionScopeManager.get(); + if (t != null) { + t.getEvent().add(tableEvent); + } else { + transactionManager.externalModification(tableEvent); + } + } + + /** + * Developer informing eBean that tables where modified outside of eBean. + * Invalidate the cache etc as required. + */ + public void externalModification(String tableName, boolean inserts, boolean updates, boolean deletes) { + + TransactionEventTable evt = new TransactionEventTable(); + evt.add(tableName, inserts, updates, deletes); + + externalModification(evt); + } + + /** + * Clear the query execution statistics. + */ + public void clearQueryStatistics() { + for (BeanDescriptor desc : getBeanDescriptors()) { + desc.clearQueryStatistics(); + } + } + + /** + * Create a new EntityBean bean. + *

+ * This will generally return a subclass of the parameter 'type' which + * additionally implements the EntityBean interface. That is, the returned + * bean is typically an instance of a dynamically generated class. + *

+ */ + @SuppressWarnings("unchecked") + public T createEntityBean(Class type) { + BeanDescriptor desc = getBeanDescriptor(type); + return (T) desc.createEntityBean(); + } + + public ObjectInputStream createProxyObjectInputStream(InputStream is) { + + try { + return new ProxyBeanObjectInputStream(is, this); + } catch (IOException e) { + throw new PersistenceException(e); + } + } + + /** + * Return a Reference bean. + *

+ * If a current transaction is active then this will check the Context of that + * transaction to see if the bean is already loaded. If it is already loaded + * then it will returned that object. + *

+ */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public T getReference(Class type, Object id) { + + if (id == null) { + throw new NullPointerException("The id is null"); + } + + BeanDescriptor desc = getBeanDescriptor(type); + // convert the id type if necessary + id = desc.convertId(id); + + Object ref = null; + PersistenceContext ctx = null; + + SpiTransaction t = transactionScopeManager.get(); + if (t != null) { + // first try the persistence context + ctx = t.getPersistenceContext(); + ref = ctx.get(type, id); + } + + if (ref == null) { + InheritInfo inheritInfo = desc.getInheritInfo(); + if (inheritInfo != null) { + // we actually need to do a query because + // we don't know the type without the + // discriminator value + BeanProperty[] idProps = desc.propertiesId(); + String idNames; + switch (idProps.length) { + case 0: + throw new PersistenceException("No ID properties for this type? " + desc); + case 1: + idNames = idProps[0].getName(); + break; + default: + idNames = Arrays.toString(idProps); + idNames = idNames.substring(1, idNames.length() - 1); + } + + // just select the id properties and + // the discriminator column (auto added) + Query query = createQuery(type); + query.select(idNames).setId(id); + + ref = query.findUnique(); + + } else { + // use the default reference options + ref = desc.createReference(vanillaRefMode, null, id, null); + } + + if (ctx != null && (ref instanceof EntityBean)) { + // Not putting a vanilla reference in the persistence context + ctx.put(id, ref); + } + } + return (T) ref; + } + + /** + * Creates a new Transaction that is NOT stored in TransactionThreadLocal. Use + * this when you want a thread to have a second independent transaction. + */ + public Transaction createTransaction() { + + return transactionManager.createTransaction(true, -1); + } + + /** + * Create a transaction additionally specify the Isolation level. + *

+ * Note that this transaction is not stored in a thread local. + *

+ */ + public Transaction createTransaction(TxIsolation isolation) { + + return transactionManager.createTransaction(true, isolation.getLevel()); + } + + /** + * Log a comment to the transaction log (of the current transaction). + */ + public void logComment(String msg) { + Transaction t = transactionScopeManager.get(); + if (t != null) { + t.log(msg); + } + } + + public T execute(TxCallable c) { + return execute(null, c); + } + + public T execute(TxScope scope, TxCallable c) { + ScopeTrans scopeTrans = createScopeTrans(scope); + try { + return c.call(); + + } catch (Error e) { + throw scopeTrans.caughtError(e); + + } catch (RuntimeException e) { + throw scopeTrans.caughtThrowable(e); + + } finally { + scopeTrans.onFinally(); + } + } + + public void execute(TxRunnable r) { + execute(null, r); + } + + public void execute(TxScope scope, TxRunnable r) { + ScopeTrans scopeTrans = createScopeTrans(scope); + try { + r.run(); + + } catch (Error e) { + throw scopeTrans.caughtError(e); + + } catch (RuntimeException e) { + throw scopeTrans.caughtThrowable(e); + + } finally { + scopeTrans.onFinally(); + } + } + + /** + * Determine whether to create a new transaction or not. + *

+ * This will also potentially throw exceptions for MANDATORY and NEVER types. + *

+ */ + private boolean createNewTransaction(SpiTransaction t, TxScope scope) { + + TxType type = scope.getType(); + switch (type) { + case REQUIRED: + return t == null; + + case REQUIRES_NEW: + return true; + + case MANDATORY: + if (t == null) { + throw new PersistenceException("Transaction missing when MANDATORY"); + } + return true; + + case NEVER: + if (t != null) { + throw new PersistenceException("Transaction exists for Transactional NEVER"); + } + return false; + + case SUPPORTS: + return false; + + case NOT_SUPPORTED: + throw new RuntimeException("NOT_SUPPORTED should already be handled?"); + + default: + throw new RuntimeException("Should never get here?"); + } + } + + public ScopeTrans createScopeTrans(TxScope txScope) { + + if (txScope == null) { + // create a TxScope with default settings + txScope = new TxScope(); + } + + SpiTransaction suspended = null; + + // get current transaction from ThreadLocal or equivalent + SpiTransaction t = transactionScopeManager.get(); + + boolean newTransaction; + if (txScope.getType().equals(TxType.NOT_SUPPORTED)) { + // Suspend existing transaction and + // run without a transaction in scope + newTransaction = false; + suspended = t; + t = null; + + } else { + // create a new Transaction based on TxType and t + newTransaction = createNewTransaction(t, txScope); + + if (newTransaction) { + // suspend existing transaction (if there is one) + suspended = t; + + // create a new transaction + int isoLevel = -1; + TxIsolation isolation = txScope.getIsolation(); + if (isolation != null) { + isoLevel = isolation.getLevel(); + } + t = transactionManager.createTransaction(true, isoLevel); + } + } + + // replace the current transaction ... ScopeTrans.onFinally() + // has the job of restoring the suspended transaction + transactionScopeManager.replace(t); + + return new ScopeTrans(rollbackOnChecked, newTransaction, t, txScope, suspended, transactionScopeManager); + } + + /** + * Returns the current transaction (or null) from the scope. + */ + public SpiTransaction getCurrentServerTransaction() { + return transactionScopeManager.get(); + } + + /** + * Start a transaction. + *

+ * Note that the transaction is stored in a ThreadLocal variable. + *

+ */ + public Transaction beginTransaction() { + // start an explicit transaction + SpiTransaction t = transactionManager.createTransaction(true, -1); + transactionScopeManager.set(t); + return t; + } + + /** + * Start a transaction with a specific Isolation Level. + *

+ * Note that the transaction is stored in a ThreadLocal variable. + *

+ */ + public Transaction beginTransaction(TxIsolation isolation) { + // start an explicit transaction + SpiTransaction t = transactionManager.createTransaction(true, isolation.getLevel()); + transactionScopeManager.set(t); + return t; + } + + /** + * Return the current transaction or null if there is not one currently in + * scope. + */ + public Transaction currentTransaction() { + return transactionScopeManager.get(); + } + + /** + * Commit the current transaction. + */ + public void commitTransaction() { + transactionScopeManager.commit(); + } + + /** + * Rollback the current transaction. + */ + public void rollbackTransaction() { + transactionScopeManager.rollback(); + } + + /** + * If the current transaction has already been committed do nothing otherwise + * rollback the transaction. + *

+ * Useful to put in a finally block to ensure the transaction is ended, rather + * than a rollbackTransaction() in each catch block. + *

+ *

+ * Code example:
+ * + *

+   * <code>
+   * Ebean.startTransaction();
+   * try {
+   * 	// do some fetching and or persisting
+   * 
+   * 	// commit at the end
+   * 	Ebean.commitTransaction();
+   * 
+   * } finally {
+   * 	// if commit didn't occur then rollback the transaction
+   * 	Ebean.endTransaction();
+   * }
+   * </code>
+   * 
+ * + *

+ */ + public void endTransaction() { + transactionScopeManager.end(); + } + + /** + * return the next unique identity value. + *

+ * Uses the BeanDescriptor deployment information to determine the sequence to + * use. + *

+ */ + public Object nextId(Class beanType) { + BeanDescriptor desc = getBeanDescriptor(beanType); + return desc.nextId(null); + } + + @SuppressWarnings("unchecked") + public void sort(List list, String sortByClause) { + + if (list == null) { + throw new NullPointerException("list is null"); + } + if (sortByClause == null) { + throw new NullPointerException("sortByClause is null"); + } + if (list.size() == 0) { + // don't need to sort an empty list + return; + } + // use first bean in the list as the correct type + Class beanType = (Class) list.get(0).getClass(); + BeanDescriptor beanDescriptor = getBeanDescriptor(beanType); + if (beanDescriptor == null) { + String m = "BeanDescriptor not found, is [" + beanType + "] an entity bean?"; + throw new PersistenceException(m); + } + beanDescriptor.sort(list, sortByClause); + } + + public Query createQuery(Class beanType) throws PersistenceException { + return createQuery(beanType, null); + } + + public Query createNamedQuery(Class beanType, String namedQuery) throws PersistenceException { + + BeanDescriptor desc = getBeanDescriptor(beanType); + if (desc == null) { + throw new PersistenceException("Is " + beanType.getName() + " an Entity Bean? BeanDescriptor not found?"); + } + DeployNamedQuery deployQuery = desc.getNamedQuery(namedQuery); + if (deployQuery == null) { + throw new PersistenceException("named query " + namedQuery + " was not found for " + desc.getFullName()); + } + + // this will parse the query + return new DefaultOrmQuery(beanType, this, expressionFactory, deployQuery); + } + + public Filter filter(Class 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); + } + return new ElFilter(desc); + } + + public CsvReader createCsvReader(Class beanType) { + BeanDescriptor descriptor = getBeanDescriptor(beanType); + if (descriptor == null) { + throw new NullPointerException("BeanDescriptor for " + beanType.getName() + " not found"); + } + return new TCsvReader(this, descriptor); + } + + public Query find(Class beanType) { + return createQuery(beanType); + } + + public Query createQuery(Class beanType, String query) { + BeanDescriptor desc = getBeanDescriptor(beanType); + if (desc == null) { + String m = beanType.getName() + " is NOT an Entity Bean registered with this server?"; + throw new PersistenceException(m); + } + switch (desc.getEntityType()) { + case SQL: + if (query != null) { + throw new PersistenceException("You must used Named queries for this Entity " + desc.getFullName()); + } + // use the "default" SqlSelect + DeployNamedQuery defaultSqlSelect = desc.getNamedQuery("default"); + return new DefaultOrmQuery(beanType, this, expressionFactory, defaultSqlSelect); + + case LDAP: + return new DefaultLdapOrmQuery(beanType, this, ldapExpressionFactory, query); + + default: + return new DefaultOrmQuery(beanType, this, expressionFactory, query); + } + } + + public Update createNamedUpdate(Class beanType, String namedUpdate) { + BeanDescriptor desc = getBeanDescriptor(beanType); + if (desc == null) { + String m = beanType.getName() + " is NOT an Entity Bean registered with this server?"; + throw new PersistenceException(m); + } + + DeployNamedUpdate deployUpdate = desc.getNamedUpdate(namedUpdate); + if (deployUpdate == null) { + throw new PersistenceException("named update " + namedUpdate + " was not found for " + desc.getFullName()); + } + + return new DefaultOrmUpdate(beanType, this, desc.getBaseTable(), deployUpdate); + } + + public Update createUpdate(Class beanType, String ormUpdate) { + BeanDescriptor desc = getBeanDescriptor(beanType); + if (desc == null) { + String m = beanType.getName() + " is NOT an Entity Bean registered with this server?"; + throw new PersistenceException(m); + } + + return new DefaultOrmUpdate(beanType, this, desc.getBaseTable(), ormUpdate); + } + + public SqlQuery createSqlQuery(String sql) { + return new DefaultRelationalQuery(this, sql); + } + + public SqlQuery createNamedSqlQuery(String namedQuery) { + DNativeQuery nq = beanDescriptorManager.getNativeQuery(namedQuery); + if (nq == null) { + throw new PersistenceException("SqlQuery " + namedQuery + " not found."); + } + return new DefaultRelationalQuery(this, nq.getQuery()); + } + + public SqlUpdate createSqlUpdate(String sql) { + return new DefaultSqlUpdate(this, sql); + } + + public CallableSql createCallableSql(String sql) { + return new DefaultCallableSql(this, sql); + } + + public SqlUpdate createNamedSqlUpdate(String namedQuery) { + DNativeQuery nq = beanDescriptorManager.getNativeQuery(namedQuery); + if (nq == null) { + throw new PersistenceException("SqlUpdate " + namedQuery + " not found."); + } + return new DefaultSqlUpdate(this, nq.getQuery()); + } + + public T find(Class beanType, Object uid) { + + return find(beanType, uid, null); + } + + /** + * Find a bean using its unique id. + */ + public T find(Class beanType, Object id, Transaction t) { + + if (id == null) { + throw new NullPointerException("The id is null"); + } + + Query query = createQuery(beanType).setId(id); + return findId(query, t); + } + + private SpiOrmQueryRequest createQueryRequest(Type type, Query query, Transaction t) { + + SpiQuery spiQuery = (SpiQuery) query; + spiQuery.setType(type); + + BeanDescriptor desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType()); + spiQuery.setBeanDescriptor(desc); + + return createQueryRequest(desc, spiQuery, t); + } + + public SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery query, Transaction t) { + + if (desc.isLdapEntityType()) { + return new LdapOrmQueryRequest(query, desc, ldapQueryEngine); + } + + if (desc.isAutoFetchTunable() && !query.isSqlSelect()) { + // its a tunable query + if (autoFetchManager.tuneQuery(query)) { + // was automatically tuned by Autofetch + } else { + // use deployment FetchType.LAZY/EAGER annotations + // to define the 'default' select clause + query.setDefaultSelectClause(); + } + } + + if (query.selectAllForLazyLoadProperty()) { + // we need to select all properties to ensure the lazy load property + // was included (was not included by default or via autofetch). + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Using selectAllForLazyLoadProperty"); + } + } + + if (true) { + // if determine cost and no origin for Autofetch + if (query.getParentNode() == null) { + CallStack callStack = createCallStack(); + query.setOrigin(callStack); + } + } + + // determine extra joins required to support where clause + // predicates on *ToMany properties + if (query.initManyWhereJoins()) { + // we need a sql distinct now + query.setDistinct(true); + } + + boolean allowOneManyFetch = true; + if (Mode.LAZYLOAD_MANY.equals(query.getMode())) { + allowOneManyFetch = false; + + } else if (query.hasMaxRowsOrFirstRow() && !query.isRawSql() && !query.isSqlSelect() && query.getBackgroundFetchAfter() == 0) { + // convert ALL fetch joins to Many's to be query joins + // so that limit offset type SQL clauses work + allowOneManyFetch = false; + } + + query.convertManyFetchJoinsToQueryJoins(allowOneManyFetch, queryBatchSize); + + SpiTransaction serverTrans = (SpiTransaction) t; + OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, serverTrans); + + BeanQueryAdapter queryAdapter = desc.getQueryAdapter(); + if (queryAdapter != null) { + // adaption of the query probably based on the + // current user + queryAdapter.preQuery(request); + } + + // the query hash after any tuning + request.calculateQueryPlanHash(); + + return request; + } + + /** + * Try to get the object out of the persistence context. + */ + @SuppressWarnings("unchecked") + private T findIdCheckPersistenceContextAndCache(Transaction transaction, BeanDescriptor beanDescriptor, SpiQuery query) { + + SpiTransaction t = (SpiTransaction) transaction; + if (t == null) { + t = getCurrentServerTransaction(); + } + PersistenceContext context = null; + if (t != null) { + // first look in the persistence context + context = t.getPersistenceContext(); + if (context != null) { + Object o = context.get(beanDescriptor.getBeanType(), query.getId()); + if (o != null) { + return (T) o; + } + } + } + + if (!beanDescriptor.calculateUseCache(query.isUseBeanCache())) { + // not using bean cache + return null; + } + + // boolean readOnly = beanDescriptor.calculateReadOnly(query.isReadOnly()); + boolean vanilla = query.isVanillaMode(vanillaMode); + Object cachedBean = beanDescriptor.cacheGetBean(query.getId(), vanilla, query.isReadOnly()); + if (cachedBean != null) { + if (context == null) { + context = new DefaultPersistenceContext(); + + } + context.put(query.getId(), cachedBean); + if (!vanilla) { + + DLoadContext loadContext = new DLoadContext(this, beanDescriptor, query.isReadOnly(), false, null, false); + loadContext.setPersistenceContext(context); + + EntityBeanIntercept ebi = ((EntityBean) cachedBean)._ebean_getIntercept(); + ebi.setPersistenceContext(context); + loadContext.register(null, ebi); + + } + } + + return (T) cachedBean; + } + + @SuppressWarnings("unchecked") + private T findId(Query query, Transaction t) { + + 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); + if (bean != null) { + return bean; + } + } + + SpiOrmQueryRequest request = createQueryRequest(desc, spiQuery, t); + + try { + request.initTransIfRequired(); + + T bean = (T) request.findId(); + request.endTransIfRequired(); + + return bean; + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + public T findUnique(Query query, Transaction t) { + + // actually a find by Id type of query... + // ... perhaps with joins and cache hints? + SpiQuery q = (SpiQuery) query; + Object id = q.getId(); + if (id != null) { + return findId(query, t); + } + + BeanDescriptor desc = beanDescriptorManager.getBeanDescriptor(q.getBeanType()); + + if (desc.calculateUseNaturalKeyCache(q.isUseBeanCache())) { + // check if it is a find by unique id + NaturalKeyBindParam keyBindParam = q.getNaturalKeyBindParam(); + if (keyBindParam != null && desc.cacheIsNaturalKey(keyBindParam.getName())) { + Object id2 = desc.cacheGetNaturalKeyId(keyBindParam.getValue()); + if (id2 != null) { + SpiQuery copy = q.copy(); + copy.convertWhereNaturalKeyToId(id2); + return findId(copy, t); + } + } + } + + // a query that is expected to return either 0 or 1 rows + List list = findList(query, t); + + if (list.size() == 0) { + return null; + } else if (list.size() > 1) { + String m = "Unique expecting 0 or 1 rows but got [" + list.size() + "]"; + throw new PersistenceException(m); + } else { + return list.get(0); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Set findSet(Query query, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.SET, query, t); + + Object result = request.getFromQueryCache(); + if (result != null) { + return (Set) result; + } + + try { + request.initTransIfRequired(); + Set set = (Set) request.findSet(); + request.endTransIfRequired(); + + return set; + + } catch (RuntimeException ex) { + // String stackTrace = throwablePrinter.print(ex); + request.rollbackTransIfRequired(); + throw ex; + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Map findMap(Query query, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.MAP, query, t); + + Object result = request.getFromQueryCache(); + if (result != null) { + return (Map) result; + } + + try { + request.initTransIfRequired(); + Map map = (Map) request.findMap(); + request.endTransIfRequired(); + + return map; + + } catch (RuntimeException ex) { + // String stackTrace = throwablePrinter.print(ex); + request.rollbackTransIfRequired(); + throw ex; + } + } + + public int findRowCount(Query query, Transaction t) { + + SpiQuery copy = ((SpiQuery) query).copy(); + return findRowCountWithCopy(copy, t); + } + + public int findRowCountWithCopy(Query query, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.ROWCOUNT, query, t); + try { + request.initTransIfRequired(); + int rowCount = request.findRowCount(); + request.endTransIfRequired(); + + return rowCount; + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + public List findIds(Query query, Transaction t) { + + SpiQuery copy = ((SpiQuery) query).copy(); + + return findIdsWithCopy(copy, t); + } + + public List findIdsWithCopy(Query query, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.ID_LIST, query, t); + try { + request.initTransIfRequired(); + List list = request.findIds(); + request.endTransIfRequired(); + + return list; + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + public FutureRowCount findFutureRowCount(Query q, Transaction t) { + + SpiQuery copy = ((SpiQuery) q).copy(); + copy.setFutureFetch(true); + + Transaction newTxn = createTransaction(); + + CallableQueryRowCount call = new CallableQueryRowCount(this, copy, newTxn); + FutureTask futureTask = new FutureTask(call); + + QueryFutureRowCount queryFuture = new QueryFutureRowCount(copy, futureTask); + backgroundExecutor.execute(futureTask); + + return queryFuture; + } + + public FutureIds findFutureIds(Query query, Transaction t) { + + SpiQuery copy = ((SpiQuery) query).copy(); + copy.setFutureFetch(true); + + // this is the list we will put the id's in ... create it now so + // it is available for other threads to read while the id query + // is still executing (we don't need to wait for it to finish) + List idList = Collections.synchronizedList(new ArrayList()); + copy.setIdList(idList); + + Transaction newTxn = createTransaction(); + + CallableQueryIds call = new CallableQueryIds(this, copy, newTxn); + FutureTask> futureTask = new FutureTask>(call); + + QueryFutureIds queryFuture = new QueryFutureIds(copy, futureTask); + + backgroundExecutor.execute(futureTask); + + return queryFuture; + } + + public FutureList findFutureList(Query query, Transaction t) { + + SpiQuery spiQuery = (SpiQuery) query; + spiQuery.setFutureFetch(true); + + if (spiQuery.getPersistenceContext() == null) { + if (t != null) { + spiQuery.setPersistenceContext(((SpiTransaction) t).getPersistenceContext()); + } else { + SpiTransaction st = getCurrentServerTransaction(); + if (st != null) { + spiQuery.setPersistenceContext(st.getPersistenceContext()); + } + } + } + + Transaction newTxn = createTransaction(); + CallableQueryList call = new CallableQueryList(this, query, newTxn); + + FutureTask> futureTask = new FutureTask>(call); + + backgroundExecutor.execute(futureTask); + + return new QueryFutureList(query, futureTask); + } + + public PagingList findPagingList(Query query, Transaction t, int pageSize) { + + SpiQuery spiQuery = (SpiQuery) query; + + // we want to use a single PersistenceContext to be used + // for all the paging queries so we make sure there is a + // PersistenceContext on the query + PersistenceContext pc = spiQuery.getPersistenceContext(); + if (pc == null) { + SpiTransaction currentTransaction = getCurrentServerTransaction(); + if (currentTransaction != null) { + pc = currentTransaction.getPersistenceContext(); + } + if (pc == null) { + pc = new DefaultPersistenceContext(); + } + spiQuery.setPersistenceContext(pc); + } + + return new LimitOffsetPagingQuery(this, spiQuery, pageSize); + } + + public void findVisit(Query query, QueryResultVisitor visitor, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.LIST, query, t); + + try { + request.initTransIfRequired(); + request.findVisit(visitor); + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + public QueryIterator findIterate(Query query, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.LIST, query, t); + + try { + request.initTransIfRequired(); + return request.findIterate(); + // request.endTransIfRequired(); + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + @SuppressWarnings("unchecked") + public List findList(Query query, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.LIST, query, t); + + Object result = request.getFromQueryCache(); + if (result != null) { + return (List) result; + } + + try { + request.initTransIfRequired(); + List list = request.findList(); + request.endTransIfRequired(); + + return list; + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + public SqlRow findUnique(SqlQuery query, Transaction t) { + + // no findId() method for SqlQuery... + // a query that is expected to return either 0 or 1 rows + List list = findList(query, t); + + if (list.size() == 0) { + return null; + + } else if (list.size() > 1) { + String m = "Unique expecting 0 or 1 rows but got [" + list.size() + "]"; + throw new PersistenceException(m); + + } else { + return list.get(0); + } + } + + public SqlFutureList findFutureList(SqlQuery query, Transaction t) { + + SpiSqlQuery spiQuery = (SpiSqlQuery) query; + spiQuery.setFutureFetch(true); + + Transaction newTxn = createTransaction(); + CallableSqlQueryList call = new CallableSqlQueryList(this, query, newTxn); + + FutureTask> futureTask = new FutureTask>(call); + + backgroundExecutor.execute(futureTask); + + return new SqlQueryFutureList(query, futureTask); + } + + public List findList(SqlQuery query, Transaction t) { + + RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t); + + try { + request.initTransIfRequired(); + List list = request.findList(); + request.endTransIfRequired(); + + return list; + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + public Set findSet(SqlQuery query, Transaction t) { + + RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t); + + try { + request.initTransIfRequired(); + Set set = request.findSet(); + request.endTransIfRequired(); + + return set; + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + public Map findMap(SqlQuery query, Transaction t) { + + RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t); + try { + request.initTransIfRequired(); + Map map = request.findMap(); + request.endTransIfRequired(); + + return map; + + } catch (RuntimeException ex) { + request.rollbackTransIfRequired(); + throw ex; + } + } + + /** + * Persist the bean by either performing an insert or update. + */ + public void save(Object bean) { + save(bean, null); + } + + /** + * Save the bean with an explicit transaction. + */ + public void save(Object bean, Transaction t) { + if (bean == null) { + throw new NullPointerException(Message.msg("bean.isnull")); + } + persister.save(bean, t); + } + + /** + * Force an update using the bean updating non-null properties. + */ + public void update(Object bean) { + update(bean, null, null); + } + + /** + * Force an update using the bean explicitly stating which properties to + * include in the update. + */ + public void update(Object bean, Set updateProps) { + update(bean, updateProps, null); + } + + /** + * Force an update using the bean updating non-null properties. + */ + public void update(Object bean, Transaction t) { + update(bean, null, t); + } + + /** + * Force an update using the bean explicitly stating which properties to + * include in the update. + */ + public void update(Object bean, Set updateProps, Transaction t) { + update(bean, updateProps, t, defaultDeleteMissingChildren, defaultUpdateNullProperties); + } + + /** + * Force an update using the bean explicitly stating which properties to + * include in the update. + */ + public void update(Object bean, Set updateProps, Transaction t, boolean deleteMissingChildren, boolean updateNullProperties) { + if (bean == null) { + throw new NullPointerException(Message.msg("bean.isnull")); + } + persister.forceUpdate(bean, updateProps, t, deleteMissingChildren, updateNullProperties); + } + + /** + * Force the bean to be saved with an explicit insert. + *

+ * Typically you would use save() and let Ebean determine if the bean should + * be inserted or updated. This can be useful when you are transferring data + * between databases and want to explicitly insert a bean into a different + * database that it came from. + *

+ */ + public void insert(Object bean) { + insert(bean, null); + } + + /** + * Force the bean to be saved with an explicit insert. + *

+ * Typically you would use save() and let Ebean determine if the bean should + * be inserted or updated. This can be useful when you are transferring data + * between databases and want to explicitly insert a bean into a different + * database that it came from. + *

+ */ + public void insert(Object bean, Transaction t) { + if (bean == null) { + throw new NullPointerException(Message.msg("bean.isnull")); + } + persister.forceInsert(bean, t); + } + + /** + * Delete the associations (from the intersection table) of a ManyToMany given + * the owner bean and the propertyName of the ManyToMany collection. + *

+ * This returns the number of associations deleted. + *

+ */ + public int deleteManyToManyAssociations(Object ownerBean, String propertyName) { + return deleteManyToManyAssociations(ownerBean, propertyName, null); + } + + /** + * Delete the associations (from the intersection table) of a ManyToMany given + * the owner bean and the propertyName of the ManyToMany collection. + *

+ * This returns the number of associations deleted. + *

+ */ + public int deleteManyToManyAssociations(Object ownerBean, String propertyName, Transaction t) { + + TransWrapper wrap = initTransIfRequired(t); + try { + SpiTransaction trans = wrap.transaction; + int rc = persister.deleteManyToManyAssociations(ownerBean, propertyName, trans); + wrap.commitIfCreated(); + return rc; + + } catch (RuntimeException e) { + wrap.rollbackIfCreated(); + throw e; + } + } + + /** + * Save the associations of a ManyToMany given the owner bean and the + * propertyName of the ManyToMany collection. + */ + public void saveManyToManyAssociations(Object ownerBean, String propertyName) { + saveManyToManyAssociations(ownerBean, propertyName, null); + } + + /** + * Save the associations of a ManyToMany given the owner bean and the + * propertyName of the ManyToMany collection. + */ + public void saveManyToManyAssociations(Object ownerBean, String propertyName, Transaction t) { + + TransWrapper wrap = initTransIfRequired(t); + try { + SpiTransaction trans = wrap.transaction; + + persister.saveManyToManyAssociations(ownerBean, propertyName, trans); + + wrap.commitIfCreated(); + + } catch (RuntimeException e) { + wrap.rollbackIfCreated(); + throw e; + } + } + + public void saveAssociation(Object ownerBean, String propertyName) { + saveAssociation(ownerBean, propertyName, null); + } + + public void saveAssociation(Object ownerBean, String propertyName, Transaction t) { + + if (ownerBean instanceof EntityBean) { + Set loadedProps = ((EntityBean) ownerBean)._ebean_getIntercept().getLoadedProps(); + if (loadedProps != null && !loadedProps.contains(propertyName)) { + // skip as property is not actually loaded in this partially + // loaded bean + logger.fine("Skip saveAssociation as property " + propertyName + " is not loaded"); + return; + } + } + + TransWrapper wrap = initTransIfRequired(t); + try { + SpiTransaction trans = wrap.transaction; + + persister.saveAssociation(ownerBean, propertyName, trans); + + wrap.commitIfCreated(); + + } catch (RuntimeException e) { + wrap.rollbackIfCreated(); + throw e; + } + } + + /** + * Perform an update or insert on each bean in the iterator. Returns the + * number of beans that where saved. + */ + public int save(Iterator it) { + return save(it, null); + } + + /** + * Perform an update or insert on each bean in the collection. Returns the + * number of beans that where saved. + */ + public int save(Collection c) { + return save(c.iterator(), null); + } + + /** + * Save all beans in the iterator with an explicit transaction. + */ + public int save(Iterator it, Transaction t) { + + TransWrapper wrap = initTransIfRequired(t); + try { + SpiTransaction trans = wrap.transaction; + int saveCount = 0; + while (it.hasNext()) { + Object bean = it.next(); + persister.save(bean, trans); + saveCount++; + } + + wrap.commitIfCreated(); + + return saveCount; + + } catch (RuntimeException e) { + wrap.rollbackIfCreated(); + throw e; + } + } + + public int delete(Class beanType, Object id) { + return delete(beanType, id, null); + } + + public int delete(Class beanType, Object id, Transaction t) { + + TransWrapper wrap = initTransIfRequired(t); + try { + SpiTransaction trans = wrap.transaction; + int rowCount = persister.delete(beanType, id, trans); + wrap.commitIfCreated(); + + return rowCount; + + } catch (RuntimeException e) { + wrap.rollbackIfCreated(); + throw e; + } + } + + public void delete(Class beanType, Collection ids) { + delete(beanType, ids, null); + } + + public void delete(Class beanType, Collection ids, Transaction t) { + + TransWrapper wrap = initTransIfRequired(t); + try { + SpiTransaction trans = wrap.transaction; + persister.deleteMany(beanType, ids, trans); + wrap.commitIfCreated(); + + } catch (RuntimeException e) { + wrap.rollbackIfCreated(); + throw e; + } + } + + /** + * Delete the bean. + */ + public void delete(Object bean) { + delete(bean, null); + } + + /** + * Delete the bean with the explicit transaction. + */ + public void delete(Object bean, Transaction t) { + if (bean == null) { + throw new NullPointerException(Message.msg("bean.isnull")); + } + persister.delete(bean, t); + } + + /** + * Delete all the beans in the iterator. + */ + public int delete(Iterator it) { + return delete(it, null); + } + + /** + * Delete all the beans in the collection. + */ + public int delete(Collection c) { + return delete(c.iterator(), null); + } + + /** + * Delete all the beans in the iterator with an explicit transaction. + */ + public int delete(Iterator it, Transaction t) { + + TransWrapper wrap = initTransIfRequired(t); + + try { + SpiTransaction trans = wrap.transaction; + int deleteCount = 0; + while (it.hasNext()) { + Object bean = it.next(); + persister.delete(bean, trans); + deleteCount++; + } + + wrap.commitIfCreated(); + + return deleteCount; + + } catch (RuntimeException e) { + wrap.rollbackIfCreated(); + throw e; + } + } + + /** + * Execute the CallableSql with an explicit transaction. + */ + public int execute(CallableSql callSql, Transaction t) { + return persister.executeCallable(callSql, t); + } + + /** + * Execute the CallableSql. + */ + public int execute(CallableSql callSql) { + return execute(callSql, null); + } + + /** + * Execute the updateSql with an explicit transaction. + */ + public int execute(SqlUpdate updSql, Transaction t) { + return persister.executeSqlUpdate(updSql, t); + } + + /** + * Execute the updateSql. + */ + public int execute(SqlUpdate updSql) { + return execute(updSql, null); + } + + /** + * Execute the updateSql with an explicit transaction. + */ + public int execute(Update update, Transaction t) { + return persister.executeOrmUpdate(update, t); + } + + /** + * Execute the orm update. + */ + public int execute(Update update) { + return execute(update, null); + } + + public BeanManager getBeanManager(Class beanClass) { + return beanDescriptorManager.getBeanManager(beanClass); + } + + /** + * Return all the BeanDescriptors. + */ + public List> getBeanDescriptors() { + return beanDescriptorManager.getBeanDescriptorList(); + } + + public void register(BeanPersistController c) { + List> list = beanDescriptorManager.getBeanDescriptorList(); + for (int i = 0; i < list.size(); i++) { + list.get(i).register(c); + } + } + + public void deregister(BeanPersistController c) { + List> list = beanDescriptorManager.getBeanDescriptorList(); + for (int i = 0; i < list.size(); i++) { + list.get(i).deregister(c); + } + } + + public boolean isSupportedType(java.lang.reflect.Type genericType) { + + TypeInfo typeInfo = ParamTypeHelper.getTypeInfo(genericType); + if (typeInfo == null) { + return false; + } + Class beanType = typeInfo.getBeanType(); + if (JsonElement.class.isAssignableFrom(beanType)) { + return true; + } + return getBeanDescriptor(typeInfo.getBeanType()) != null; + } + + public Object getBeanId(Object bean) { + BeanDescriptor desc = getBeanDescriptor(bean.getClass()); + if (desc == null) { + String m = bean.getClass().getName() + " is NOT an Entity Bean registered with this server?"; + throw new PersistenceException(m); + } + + return desc.getId(bean); + } + + /** + * Return the BeanDescriptor for a given type of bean. + */ + public BeanDescriptor getBeanDescriptor(Class beanClass) { + return beanDescriptorManager.getBeanDescriptor(beanClass); + } + + /** + * Return the BeanDescriptor's for a given table name. + */ + public List> getBeanDescriptors(String tableName) { + return beanDescriptorManager.getBeanDescriptors(tableName); + } + + /** + * Return the BeanDescriptor using its unique id. + */ + public BeanDescriptor getBeanDescriptorById(String descriptorId) { + return beanDescriptorManager.getBeanDescriptorById(descriptorId); + } + + /** + * Another server in the cluster sent this event so that we can inform local + * BeanListeners of inserts updates and deletes that occurred remotely (on + * another server in the cluster). + */ + public void remoteTransactionEvent(RemoteTransactionEvent event) { + transactionManager.remoteTransactionEvent(event); + } + + /** + * Create a transaction if one is not currently active in the + * TransactionThreadLocal. + *

+ * Returns a TransWrapper which contains the wasCreated flag. If this is true + * then the transaction was created for this request in which case it will + * need to be committed after the request has been processed. + *

+ */ + TransWrapper initTransIfRequired(Transaction t) { + + if (t != null) { + return new TransWrapper((SpiTransaction) t, false); + } + + boolean wasCreated = false; + SpiTransaction trans = transactionScopeManager.get(); + if (trans == null) { + // create a transaction + trans = transactionManager.createTransaction(false, -1); + wasCreated = true; + } + return new TransWrapper(trans, wasCreated); + } + + public SpiTransaction createServerTransaction(boolean isExplicit, int isolationLevel) { + return transactionManager.createTransaction(isExplicit, isolationLevel); + } + + public SpiTransaction createQueryTransaction() { + return transactionManager.createQueryTransaction(); + } + + private static final int IGNORE_LEADING_ELEMENTS = 5; + private static final String AVAJE_EBEAN = Ebean.class.getName().substring(0, 15); + + /** + * Create a CallStack object. + *

+ * This trims off the avaje ebean part of the stack trace so that the first + * element in the CallStack should be application code. + *

+ */ + public CallStack createCallStack() { + + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + + // ignore the first 6 as they are always avaje stack elements + int startIndex = IGNORE_LEADING_ELEMENTS; + + // find the first non-avaje stackElement + for (; startIndex < stackTrace.length; startIndex++) { + if (!stackTrace[startIndex].getClassName().startsWith(AVAJE_EBEAN)) { + break; + } + } + + int stackLength = stackTrace.length - startIndex; + if (stackLength > maxCallStack) { + // maximum of maxCallStack stackTrace elements + stackLength = maxCallStack; + } + + // create the 'interesting' part of the stackTrace + StackTraceElement[] finalTrace = new StackTraceElement[stackLength]; + for (int i = 0; i < stackLength; i++) { + finalTrace[i] = stackTrace[i + startIndex]; + } + + if (stackLength < 1) { + // this should not really happen + throw new RuntimeException("StackTraceElement size 0? stack: " + Arrays.toString(stackTrace)); + } + + return new CallStack(finalTrace); + } + + public JsonContext createJsonContext() { + // immutable thread safe so return shared instance + return jsonContext; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java new file mode 100644 index 000000000..015369434 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java @@ -0,0 +1,518 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.cache.ServerCacheFactory; +import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.cache.ServerCacheOptions; +import com.avaje.ebean.common.BootupEbeanManager; +import com.avaje.ebean.config.DataSourceConfig; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.PstmtDelegate; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.UnderscoreNamingConvention; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.cache.DefaultServerCacheFactory; +import com.avaje.ebeaninternal.server.cache.DefaultServerCacheManager; +import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.jdbc.OraclePstmtBatch; +import com.avaje.ebeaninternal.server.jdbc.StandardPstmtDelegate; +import com.avaje.ebeaninternal.server.lib.ShutdownManager; +import com.avaje.ebeaninternal.server.lib.sql.DataSourceGlobalManager; +import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool; +import com.avaje.ebeaninternal.server.lib.thread.ThreadPool; +import com.avaje.ebeaninternal.server.lib.thread.ThreadPoolManager; + +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.persistence.PersistenceException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Default Server side implementation of ServerFactory. + */ +public class DefaultServerFactory implements BootupEbeanManager { + + private static final Logger logger = Logger.getLogger(DefaultServerFactory.class.getName()); + + private final ClusterManager clusterManager; + + private final JndiDataSourceLookup jndiDataSourceFactory; + + private final BootupClassPathSearch bootupClassSearch; + + private final AtomicInteger serverId = new AtomicInteger(1); + + private final XmlConfigLoader xmlConfigLoader; + + private final XmlConfig xmlConfig; + + public DefaultServerFactory() { + + this.clusterManager = new ClusterManager(); + this.jndiDataSourceFactory = new JndiDataSourceLookup(); + + List packages = getSearchJarsPackages(GlobalProperties.get("ebean.search.packages", null)); + List jars = getSearchJarsPackages(GlobalProperties.get("ebean.search.jars", null)); + + this.bootupClassSearch = new BootupClassPathSearch(null, packages, jars); + this.xmlConfigLoader = new XmlConfigLoader(null); + + this.xmlConfig = xmlConfigLoader.load(); + + // register so that we can shutdown any Ebean wide + // resources such as clustering + ShutdownManager.registerServerFactory(this); + } + + private List getSearchJarsPackages(String searchPackages) { + + List hitList = new ArrayList(); + + if (searchPackages != null) { + + String[] entries = searchPackages.split("[ ,;]"); + for (int i = 0; i < entries.length; i++) { + hitList.add(entries[i].trim()); + } + } + return hitList; + } + + public void shutdown() { + clusterManager.shutdown(); + } + + /** + * Create the server reading configuration information from ebean.properties. + */ + public SpiEbeanServer createServer(String name) { + + ConfigBuilder b = new ConfigBuilder(); + ServerConfig config = b.build(name); + + return createServer(config); + } + + private SpiBackgroundExecutor createBackgroundExecutor(ServerConfig serverConfig, int uniqueServerId) { + + String namePrefix = "Ebean-" + serverConfig.getName(); + + // the size of the pool for executing periodic tasks (such as cache + // flushing) + int schedulePoolSize = GlobalProperties.getInt("backgroundExecutor.schedulePoolsize", 1); + + // the side of the main pool for immediate background task execution + int minPoolSize = GlobalProperties.getInt("backgroundExecutor.minPoolSize", 1); + int poolSize = GlobalProperties.getInt("backgroundExecutor.poolsize", 20); + int maxPoolSize = GlobalProperties.getInt("backgroundExecutor.maxPoolSize", poolSize); + + int idleSecs = GlobalProperties.getInt("backgroundExecutor.idlesecs", 60); + int shutdownSecs = GlobalProperties.getInt("backgroundExecutor.shutdownSecs", 30); + + boolean useTrad = GlobalProperties.getBoolean("backgroundExecutor.traditional", true); + + if (useTrad) { + // this pool will use Idle seconds between min and max so I think it is + // better + // as it will let the thread count float between the min and max + ThreadPool pool = ThreadPoolManager.getThreadPool(namePrefix); + pool.setMinSize(minPoolSize); + pool.setMaxSize(maxPoolSize); + pool.setMaxIdleTime(idleSecs * 1000); + return new TraditionalBackgroundExecutor(pool, schedulePoolSize, shutdownSecs, namePrefix); + } else { + return new DefaultBackgroundExecutor(poolSize, schedulePoolSize, idleSecs, shutdownSecs, namePrefix); + } + } + + /** + * Create the implementation from the configuration. + */ + public SpiEbeanServer createServer(ServerConfig serverConfig) { + + synchronized (this) { + setNamingConvention(serverConfig); + + BootupClasses bootupClasses = getBootupClasses(serverConfig); + + setDataSource(serverConfig); + // check the autoCommit and Transaction Isolation + boolean online = checkDataSource(serverConfig); + + // determine database platform (Oracle etc) + setDatabasePlatform(serverConfig); + if (serverConfig.getDbEncrypt() != null) { + // use a configured DbEncrypt rather than the platform default + serverConfig.getDatabasePlatform().setDbEncrypt(serverConfig.getDbEncrypt()); + } + + DatabasePlatform dbPlatform = serverConfig.getDatabasePlatform(); + + PstmtBatch pstmtBatch = null; + + if (dbPlatform.getName().startsWith("oracle")) { + PstmtDelegate pstmtDelegate = serverConfig.getPstmtDelegate(); + if (pstmtDelegate == null) { + // try to provide the + pstmtDelegate = getOraclePstmtDelegate(serverConfig.getDataSource()); + } + if (pstmtDelegate != null) { + // We can support JDBC batching with Oracle + // via OraclePreparedStatement + pstmtBatch = new OraclePstmtBatch(pstmtDelegate); + } + if (pstmtBatch == null) { + // We can not support JDBC batching with Oracle + logger.warning("Can not support JDBC batching with Oracle without a PstmtDelegate"); + serverConfig.setPersistBatching(false); + } + } + + // inform the NamingConvention of the associated DatabasePlaform + serverConfig.getNamingConvention().setDatabasePlatform(serverConfig.getDatabasePlatform()); + + ServerCacheManager cacheManager = getCacheManager(serverConfig); + + int uniqueServerId = serverId.incrementAndGet(); + SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig, uniqueServerId); + + InternalConfiguration c = new InternalConfiguration(xmlConfig, clusterManager, cacheManager, bgExecutor, serverConfig, bootupClasses, + pstmtBatch); + + DefaultServer server = new DefaultServer(c, cacheManager); + + cacheManager.init(server); + + MBeanServer mbeanServer; + ArrayList list = MBeanServerFactory.findMBeanServer(null); + if (list.size() == 0) { + // probably not running in a server + mbeanServer = MBeanServerFactory.createMBeanServer(); + } else { + // use the first MBeanServer + mbeanServer = (MBeanServer) list.get(0); + } + + server.registerMBeans(mbeanServer, uniqueServerId); + + // generate and run DDL if required + executeDDL(server, online); + + // initialise prior to registering with clusterManager + server.initialise(); + + if (online) { + if (clusterManager.isClustering()) { + // register the server once it has been created + clusterManager.registerServer(server); + } + + // warm the cache in 30 seconds + int delaySecs = GlobalProperties.getInt("ebean.cacheWarmingDelay", 30); + long sleepMillis = 1000 * delaySecs; + + if (sleepMillis > 0) { + Timer t = new Timer("EbeanCacheWarmer", true); + t.schedule(new CacheWarmer(sleepMillis, server), sleepMillis); + } + } + + // start any services after registering with clusterManager + server.start(); + return server; + } + } + + private PstmtDelegate getOraclePstmtDelegate(DataSource ds) { + + if (ds instanceof DataSourcePool) { + // Using Ebean's own DataSource implementation + return new StandardPstmtDelegate(); + } + + return null; + } + + /** + * Create and return the CacheManager. + */ + private ServerCacheManager getCacheManager(ServerConfig serverConfig) { + + ServerCacheManager serverCacheManager = serverConfig.getServerCacheManager(); + if (serverCacheManager != null) { + return serverCacheManager; + } + + // reasonable default settings are for a cache per bean type + ServerCacheOptions beanOptions = new ServerCacheOptions(); + beanOptions.setMaxSize(GlobalProperties.getInt("cache.maxSize", 1000)); + // maxIdleTime 10 minutes + beanOptions.setMaxIdleSecs(GlobalProperties.getInt("cache.maxIdleTime", 60 * 10)); + // maxTimeToLive 6 hrs + beanOptions.setMaxSecsToLive(GlobalProperties.getInt("cache.maxTimeToLive", 60 * 60 * 6)); + + // reasonable default settings for the query cache per bean type + ServerCacheOptions queryOptions = new ServerCacheOptions(); + queryOptions.setMaxSize(GlobalProperties.getInt("querycache.maxSize", 100)); + // maxIdleTime 10 minutes + queryOptions.setMaxIdleSecs(GlobalProperties.getInt("querycache.maxIdleTime", 60 * 10)); + // maxTimeToLive 6 hours + queryOptions.setMaxSecsToLive(GlobalProperties.getInt("querycache.maxTimeToLive", 60 * 60 * 6)); + + ServerCacheFactory cacheFactory = serverConfig.getServerCacheFactory(); + if (cacheFactory == null) { + cacheFactory = new DefaultServerCacheFactory(); + } + + return new DefaultServerCacheManager(cacheFactory, beanOptions, queryOptions); + } + + /** + * Get the entities, scalarTypes, Listeners etc combining the class registered + * ones with the already created instances. + */ + private BootupClasses getBootupClasses(ServerConfig serverConfig) { + + BootupClasses bootupClasses = getBootupClasses1(serverConfig); + bootupClasses.addPersistControllers(serverConfig.getPersistControllers()); + bootupClasses.addTransactionEventListeners(serverConfig.getTransactionEventListeners()); + bootupClasses.addPersistListeners(serverConfig.getPersistListeners()); + bootupClasses.addQueryAdapters(serverConfig.getQueryAdapters()); + bootupClasses.addServerConfigStartup(serverConfig.getServerConfigStartupListeners()); + + // run any ServerConfigStartup instances + bootupClasses.runServerConfigStartup(serverConfig); + return bootupClasses; + } + + /** + * Get the class based entities, scalarTypes, Listeners etc. + */ + private BootupClasses getBootupClasses1(ServerConfig serverConfig) { + + List> entityClasses = serverConfig.getClasses(); + if (entityClasses != null && entityClasses.size() > 0) { + // use classes we explicitly added via configuration + return new BootupClasses(serverConfig.getClasses()); + } + + List jars = serverConfig.getJars(); + List packages = serverConfig.getPackages(); + + if ((packages != null && !packages.isEmpty()) || (jars != null && !jars.isEmpty())) { + // filter by package name + BootupClassPathSearch search = new BootupClassPathSearch(null, packages, jars); + return search.getBootupClasses(); + } + + // just use classes we can find via class path search + return bootupClassSearch.getBootupClasses().createCopy(); + } + + /** + * Execute the DDL if required. + */ + private void executeDDL(SpiEbeanServer server, boolean online) { + + server.getDdlGenerator().execute(online); + } + + /** + * Set the naming convention to underscore if it has not already been set. + */ + private void setNamingConvention(ServerConfig config) { + if (config.getNamingConvention() == null) { + UnderscoreNamingConvention nc = new UnderscoreNamingConvention(); + config.setNamingConvention(nc); + + String v = config.getProperty("namingConvention.useForeignKeyPrefix"); + if (v != null) { + boolean useForeignKeyPrefix = Boolean.valueOf(v); + nc.setUseForeignKeyPrefix(useForeignKeyPrefix); + } + + String sequenceFormat = config.getProperty("namingConvention.sequenceFormat"); + if (sequenceFormat != null) { + nc.setSequenceFormat(sequenceFormat); + } + } + } + + /** + * Set the DatabasePlatform if it has not already been set. + */ + private void setDatabasePlatform(ServerConfig config) { + + DatabasePlatform dbPlatform = config.getDatabasePlatform(); + if (dbPlatform == null) { + + DatabasePlatformFactory factory = new DatabasePlatformFactory(); + + DatabasePlatform db = factory.create(config); + config.setDatabasePlatform(db); + logger.info("DatabasePlatform name:" + config.getName() + " platform:" + db.getName()); + } + } + + /** + * Set the DataSource if it has not already been set. + */ + private void setDataSource(ServerConfig config) { + if (config.getDataSource() == null) { + DataSource ds = getDataSourceFromConfig(config); + config.setDataSource(ds); + } + } + + private DataSource getDataSourceFromConfig(ServerConfig config) { + + DataSource ds = null; + + if (config.getDataSourceJndiName() != null) { + ds = jndiDataSourceFactory.lookup(config.getDataSourceJndiName()); + if (ds == null) { + String m = "JNDI lookup for DataSource " + config.getDataSourceJndiName() + " returned null."; + throw new PersistenceException(m); + } else { + return ds; + } + } + + DataSourceConfig dsConfig = config.getDataSourceConfig(); + if (dsConfig == null) { + String m = "No DataSourceConfig definded for " + config.getName(); + throw new PersistenceException(m); + } + + if (dsConfig.isOffline()) { + if (config.getDatabasePlatformName() == null) { + String m = "You MUST specify a DatabasePlatformName on ServerConfig when offline"; + throw new PersistenceException(m); + } + return null; + } + + if (dsConfig.getHeartbeatSql() == null) { + // use default heartbeatSql from the DatabasePlatform + String heartbeatSql = getHeartbeatSql(dsConfig.getDriver()); + dsConfig.setHeartbeatSql(heartbeatSql); + } + + return DataSourceGlobalManager.getDataSource(config.getName(), dsConfig); + } + + /** + * Return a heartbeatSql depending on the jdbc driver name. + */ + private String getHeartbeatSql(String driver) { + if (driver != null) { + String d = driver.toLowerCase(); + if (d.contains("oracle")) { + return "select 'x' from dual"; + } + if (d.contains(".h2.") || d.contains(".mysql.") || d.contains("postgre")) { + return "select 1"; + } + } + return null; + } + + /** + * Check the autoCommit and Transaction Isolation levels of the DataSource. + *

+ * If autoCommit is true this could be a real problem. + *

+ *

+ * If the Isolation level is not READ_COMMITED then optimistic concurrency + * checking may not work as expected. + *

+ */ + private boolean checkDataSource(ServerConfig serverConfig) { + + if (serverConfig.getDataSource() == null) { + if (serverConfig.getDataSourceConfig().isOffline()) { + // this is ok - offline DDL generation etc + return false; + } + throw new RuntimeException("DataSource not set?"); + } + + Connection c = null; + try { + c = serverConfig.getDataSource().getConnection(); + + if (c.getAutoCommit()) { + String m = "DataSource [" + serverConfig.getName() + "] has autoCommit defaulting to true!"; + logger.warning(m); + } + + return true; + + } catch (SQLException ex) { + throw new PersistenceException(ex); + + } finally { + if (c != null) { + try { + c.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + } + + private static class CacheWarmer extends TimerTask { + + private static final Logger log = Logger.getLogger(CacheWarmer.class.getName()); + + private final long sleepMillis; + private final EbeanServer server; + + CacheWarmer(long sleepMillis, EbeanServer server) { + this.sleepMillis = sleepMillis; + this.server = server; + } + + public void run() { + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException e) { + String msg = "Error while sleeping prior to cache warming"; + log.log(Level.SEVERE, msg, e); + } + server.runCacheWarming(); + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java new file mode 100644 index 000000000..f87037df8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java @@ -0,0 +1,246 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.io.Serializable; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Update; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiSqlUpdate; + +/** + * A SQL Update Delete or Insert statement that can be executed. For the times + * when you want to use Sql DML rather than a ORM bean approach. Refer to the + * Ebean execute() method. + *

+ * There is also {@link Update} which is similar except should use logical bean and + * property names rather than physical table and column names. + *

+ *

+ * SqlUpdate is designed for general DML sql and CallableSql is + * designed for use with stored procedures. + *

+ * + *
+ * // String sql = "update f_topic set post_count = :count where id = :topicId";
+ * 
+ * SqlUpdate update = new SqlUpdate(sql);
+ * update.setParameter("count", 1);
+ * update.setParameter("topicId", 50);
+ * 
+ * int modifiedCount = Ebean.execute(update);
+ * 
+ * + *

+ * Note that when the SqlUpdate is executed via Ebean.execute() the sql is + * parsed to determine if it is an update, delete or insert. In addition the + * table modified is deduced. If isAutoTableMod() is true, then this + * is then added to the TransactionEvent and cache invalidation etc is + * maintained. This means you don't need to use the Ebean.externalModification() + * method as this has already been done. + *

+ *

+ * You can sql.setAutoTableMod(false); to stop the automatic table modification + *

+ *

+ * EXAMPLE: Using JDBC batching with SqlUpdate + *

+ *
+ * 
+ * String data = "This is a simple test of the batch processing"
+ * 		+ " mode and the transaction execute batch method";
+ * 
+ * String[] da = data.split(" ");
+ * 
+ * String sql = "insert into junk (word) values (?)";
+ * 
+ * SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql);
+ * 
+ * Transaction t = Ebean.beginTransaction();
+ * t.setBatchMode(true);
+ * t.setBatchSize(3);
+ * try {
+ * 	for (int i = 0; i < da.length; i++) {
+ * 
+ * 		sqlUpdate.setParameter(1, da[i]);
+ * 		sqlUpdate.execute();
+ * 	}
+ * 
+ * 	// NB: commit implicitly flushes the batch 
+ * 	Ebean.commitTransaction();
+ * 
+ * } finally {
+ * 	Ebean.endTransaction();
+ * }
+ * 
+ * @see com.avaje.ebean.CallableSql + * @see com.avaje.ebean.Ebean#execute(SqlUpdate) + */ +public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { + + private static final long serialVersionUID = -6493829438421253102L; + + private transient final EbeanServer server; + + /** + * The parameters used to bind to the sql. + */ + private final BindParams bindParams; + + /** + * The sql update or delete statement. + */ + private final String sql; + + /** + * Some descriptive text that can be put into the transaction log. + */ + private String label = ""; + + /** + * The statement execution timeout. + */ + private int timeout; + + /** + * Automatically detect the table being modified by this sql. This will + * register this information so that eBean invalidates cached objects if + * required. + */ + private boolean isAutoTableMod = true; + + /** + * Helper to add positioned parameters in order. + */ + private int addPos; + + /** + * Create with server sql and bindParams object. + *

+ * Useful if you are building the sql and binding parameters at the + * same time. + *

+ */ + public DefaultSqlUpdate(EbeanServer server, String sql, BindParams bindParams) { + this.server = server; + this.sql = sql; + this.bindParams = bindParams; + } + + /** + * Create with a specific server. This means you can use the + * SqlUpdate.execute() method. + */ + public DefaultSqlUpdate(EbeanServer server, String sql) { + this(server, sql, new BindParams()); + } + + /** + * Create with some sql. + */ + public DefaultSqlUpdate(String sql) { + this(null, sql, new BindParams()); + } + + public int execute() { + if (server != null) { + return server.execute(this); + } else { + // Hopefully this doesn't catch anyone out... + return Ebean.execute(this); + } + } + + public boolean isAutoTableMod() { + return isAutoTableMod; + } + + public SqlUpdate setAutoTableMod(boolean isAutoTableMod) { + this.isAutoTableMod = isAutoTableMod; + return this; + } + + public String getLabel() { + return label; + } + + public SqlUpdate setLabel(String label) { + this.label = label; + return this; + } + + public String getSql() { + return sql; + } + + public int getTimeout() { + return timeout; + } + + public SqlUpdate setTimeout(int secs) { + this.timeout = secs; + return this; + } + + public SqlUpdate addParameter(Object value) { + return setParameter(++addPos, value); + } + + public SqlUpdate setParameter(int position, Object value) { + bindParams.setParameter(position, value); + return this; + } + + public SqlUpdate setNull(int position, int jdbcType) { + bindParams.setNullParameter(position, jdbcType); + return this; + } + + public SqlUpdate setNullParameter(int position, int jdbcType) { + bindParams.setNullParameter(position, jdbcType); + return this; + } + + public SqlUpdate setParameter(String name, Object param) { + bindParams.setParameter(name, param); + return this; + } + + public SqlUpdate setNull(String name, int jdbcType) { + bindParams.setNullParameter(name, jdbcType); + return this; + } + + public SqlUpdate setNullParameter(String name, int jdbcType) { + bindParams.setNullParameter(name, jdbcType); + return this; + } + + /** + * Return the bind parameters. + */ + public BindParams getBindParams() { + return bindParams; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java b/src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java new file mode 100644 index 000000000..77f20e37e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java @@ -0,0 +1,177 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.LinkedHashMap; +import java.util.Map; + +import com.avaje.ebean.ValuePair; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.util.ValueUtil; + +/** + * Helper to perform a diff given two beans of the same type. + *

+ * This intentionally does not include any OneToMany or ManyToMany properties. + *

+ */ +public class DiffHelp { + + + /** + * Return a map of the differences between a and b. + *

+ * A and B must be of the same type. B can be null, in which case the + * 'OldValues' of a is used to compare with (as B). + *

+ *

+ * This intentionally does not include as OneToMany or ManyToMany + * properties. + *

+ */ + public Map diff(Object a, Object b, BeanDescriptor desc) { + + boolean oldValues = false; + if (b == null) { + // get the old values from a + if (a instanceof EntityBean) { + EntityBean eb = (EntityBean) a; + b = eb._ebean_getIntercept().getOldValues(); + oldValues = true; + } + } + + Map map = new LinkedHashMap(); + + if (b == null) { + return map; + } + + // check the simple properties + BeanProperty[] base = desc.propertiesBaseScalar(); + for (int i = 0; i < base.length; i++) { + + Object aval = base[i].getValue(a); + Object bval = base[i].getValue(b); + if (!ValueUtil.areEqual(aval, bval)) { + map.put(base[i].getName(), new ValuePair(aval, bval)); + } + } + + diffAssocOne(a, b, desc, map); + diffEmbedded(a, b, desc, map, oldValues); + + return map; + } + + /** + * Check the Embedded bean properties for differences. + *

+ * If ANY of the properties are different then the whole Embedded bean is + * determined to be different as is added to the map. + *

+ */ + private void diffEmbedded(Object a, Object b, BeanDescriptor desc, Map map, + boolean oldValues) { + + BeanPropertyAssocOne[] emb = desc.propertiesEmbedded(); + + for (int i = 0; i < emb.length; i++) { + Object aval = emb[i].getValue(a); + Object bval = emb[i].getValue(b); + if (oldValues) { + bval = ((EntityBean) bval)._ebean_getIntercept().getOldValues(); + if (bval == null) { + continue; + } + } + + if (!isBothNull(aval, bval)) { + if (isDiffNull(aval, bval)) { + // one of the embedded beans is null + map.put(emb[i].getName(), new ValuePair(aval, bval)); + + } else { + // if ANY of the properties in an Embedded bean is + // different, treat the whole bean as being different + BeanProperty[] props = emb[i].getProperties(); + for (int j = 0; j < props.length; j++) { + Object aEmbPropVal = props[j].getValue(aval); + Object bEmbPropVal = props[j].getValue(bval); + if (!ValueUtil.areEqual(aEmbPropVal, bEmbPropVal)) { + + // if one prop is different put the + // embedded bean in the map + map.put(emb[i].getName(), new ValuePair(aval, bval)); + } + } + } + } + } + } + + /** + * If the properties are different by null OR if the id value is different, + * then add the Assoc One bean to the map. + */ + private void diffAssocOne(Object a, Object b, BeanDescriptor desc, Map map) { + + BeanPropertyAssocOne[] ones = desc.propertiesOne(); + + for (int i = 0; i < ones.length; i++) { + Object aval = ones[i].getValue(a); + Object bval = ones[i].getValue(b); + + if (!isBothNull(aval, bval)) { + if (isDiffNull(aval, bval)) { + // one of them is/was null + map.put(ones[i].getName(), new ValuePair(aval, bval)); + + } else { + // check to see if the Id properties + // are different + BeanDescriptor oneDesc = ones[i].getTargetDescriptor(); + Object aOneId = oneDesc.getId(aval); + Object bOneId = oneDesc.getId(bval); + + if (!ValueUtil.areEqual(aOneId, bOneId)) { + // the ids are different + map.put(ones[i].getName(), new ValuePair(aval, bval)); + } + } + } + } + } + + private boolean isBothNull(Object aval, Object bval) { + return aval == null && bval == null; + } + + private boolean isDiffNull(Object aval, Object bval) { + if (aval == null) { + return bval != null; + } else { + return bval == null; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternString.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternString.java new file mode 100644 index 000000000..015f7317f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternString.java @@ -0,0 +1,37 @@ +package com.avaje.ebeaninternal.server.core; + +import java.util.HashMap; + +/** + * Used to reduce memory consumption of strings used in deployment processing. + *

+ * Using this for now instead of String.intern() to avoid any unexpected + * increase in PermGen space. + *

+ */ +public final class InternString { + + private static HashMap map = new HashMap(); + + + /** + * Return the shared instance of this string. + */ + public static String intern(String s){ + + if (s == null){ + return null; + } + + synchronized (map) { + String v = map.get(s); + if (v != null){ + return v; + } else { + map.put(s, s); + return s; + } + + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java new file mode 100644 index 000000000..ba8c7859a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -0,0 +1,295 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.logging.Logger; + +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.config.ExternalTransactionManager; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.ldap.LdapConfig; +import com.avaje.ebean.config.ldap.LdapContextFactory; +import com.avaje.ebean.text.json.JsonContext; +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManagerFactory; +import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeaninternal.server.deploy.DeployOrmXml; +import com.avaje.ebeaninternal.server.deploy.parse.DeployCreateProperties; +import com.avaje.ebeaninternal.server.deploy.parse.DeployInherit; +import com.avaje.ebeaninternal.server.deploy.parse.DeployUtil; +import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory; +import com.avaje.ebeaninternal.server.jmx.MAdminLogging; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.persist.DefaultPersister; +import com.avaje.ebeaninternal.server.query.CQueryEngine; +import com.avaje.ebeaninternal.server.query.DefaultOrmQueryEngine; +import com.avaje.ebeaninternal.server.query.DefaultRelationalQueryEngine; +import com.avaje.ebeaninternal.server.resource.ResourceManager; +import com.avaje.ebeaninternal.server.resource.ResourceManagerFactory; +import com.avaje.ebeaninternal.server.subclass.SubClassManager; +import com.avaje.ebeaninternal.server.text.json.DJsonContext; +import com.avaje.ebeaninternal.server.text.json.DefaultJsonValueAdapter; +import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager; +import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager; +import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager; +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; + +/** + * Used to extend the ServerConfig with additional objects used to configure and + * construct an EbeanServer. + * + * @author rbygrave + */ +public class InternalConfiguration { + + private static final Logger logger = Logger.getLogger(InternalConfiguration.class.getName()); + + private final ServerConfig serverConfig; + + private final BootupClasses bootupClasses; + + private final SubClassManager subClassManager; + + private final DeployInherit deployInherit; + + private final ResourceManager resourceManager; + + private final DeployOrmXml deployOrmXml; + + private final TypeManager typeManager; + + private final Binder binder; + + private final DeployCreateProperties deployCreateProperties; + + private final DeployUtil deployUtil; + + private final BeanDescriptorManager beanDescriptorManager; + + private final MAdminLogging logControl; + + private final DebugLazyLoad debugLazyLoad; + + private final TransactionManager transactionManager; + + private final TransactionScopeManager transactionScopeManager; + + private final CQueryEngine cQueryEngine; + + private final ClusterManager clusterManager; + + private final ServerCacheManager cacheManager; + + private final ExpressionFactory expressionFactory; + + private final SpiBackgroundExecutor backgroundExecutor; + + private final PstmtBatch pstmtBatch; + + private final XmlConfig xmlConfig; + + public InternalConfiguration(XmlConfig xmlConfig, ClusterManager clusterManager, ServerCacheManager cacheManager, + SpiBackgroundExecutor backgroundExecutor, ServerConfig serverConfig, BootupClasses bootupClasses, PstmtBatch pstmtBatch) { + + this.xmlConfig = xmlConfig; + this.pstmtBatch = pstmtBatch; + this.clusterManager = clusterManager; + this.backgroundExecutor = backgroundExecutor; + this.cacheManager = cacheManager; + this.serverConfig = serverConfig; + this.bootupClasses = bootupClasses; + this.expressionFactory = new DefaultExpressionFactory(); + + this.subClassManager = new SubClassManager(serverConfig); + + this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses); + this.binder = new Binder(typeManager); + + this.resourceManager = ResourceManagerFactory.createResourceManager(serverConfig); + this.deployOrmXml = new DeployOrmXml(resourceManager.getResourceSource()); + this.deployInherit = new DeployInherit(bootupClasses); + + this.deployCreateProperties = new DeployCreateProperties(typeManager); + this.deployUtil = new DeployUtil(typeManager, serverConfig); + + this.beanDescriptorManager = new BeanDescriptorManager(this); + beanDescriptorManager.deploy(); + + this.debugLazyLoad = new DebugLazyLoad(serverConfig.isDebugLazyLoad()); + + this.transactionManager = new TransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, + this.getBootupClasses()); + + this.logControl = new MAdminLogging(serverConfig, transactionManager); + + this.cQueryEngine = new CQueryEngine(serverConfig.getDatabasePlatform(), logControl, binder, backgroundExecutor); + + 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); + } + + } + + public JsonContext createJsonContext(SpiEbeanServer server) { + + String s = serverConfig.getProperty("json.pretty", "false"); + boolean dfltPretty = "true".equalsIgnoreCase(s); + + s = serverConfig.getProperty("json.jsonValueAdapter", null); + + JsonValueAdapter va = new DefaultJsonValueAdapter(); + if (s != null) { + va = (JsonValueAdapter) ClassUtil.newInstance(s, this.getClass()); + } + return new DJsonContext(server, va, dfltPretty); + } + + public XmlConfig getXmlConfig() { + return xmlConfig; + } + + public AutoFetchManager createAutoFetchManager(SpiEbeanServer server) { + return AutoFetchManagerFactory.create(server, serverConfig, resourceManager); + } + + public RelationalQueryEngine createRelationalQueryEngine() { + return new DefaultRelationalQueryEngine(logControl, binder, serverConfig.getDatabaseBooleanTrue()); + } + + public OrmQueryEngine createOrmQueryEngine() { + return new DefaultOrmQueryEngine(beanDescriptorManager, cQueryEngine); + } + + public Persister createPersister(SpiEbeanServer server) { + LdapContextFactory ldapCtxFactory = null; + LdapConfig ldapConfig = serverConfig.getLdapConfig(); + if (ldapConfig != null) { + ldapCtxFactory = ldapConfig.getContextFactory(); + } + return new DefaultPersister(server, serverConfig.isValidateOnSave(), binder, beanDescriptorManager, pstmtBatch, ldapCtxFactory); + } + + public PstmtBatch getPstmtBatch() { + return pstmtBatch; + } + + public ServerCacheManager getCacheManager() { + return cacheManager; + } + + public BootupClasses getBootupClasses() { + return bootupClasses; + } + + public DatabasePlatform getDatabasePlatform() { + return serverConfig.getDatabasePlatform(); + } + + public ServerConfig getServerConfig() { + return serverConfig; + } + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } + + public TypeManager getTypeManager() { + return typeManager; + } + + public Binder getBinder() { + return binder; + } + + public BeanDescriptorManager getBeanDescriptorManager() { + return beanDescriptorManager; + } + + public SubClassManager getSubClassManager() { + return subClassManager; + } + + public DeployInherit getDeployInherit() { + return deployInherit; + } + + public ResourceManager getResourceManager() { + return resourceManager; + } + + public DeployOrmXml getDeployOrmXml() { + return deployOrmXml; + } + + public DeployCreateProperties getDeployCreateProperties() { + return deployCreateProperties; + } + + public DeployUtil getDeployUtil() { + return deployUtil; + } + + public MAdminLogging getLogControl() { + return logControl; + } + + public TransactionManager getTransactionManager() { + return transactionManager; + } + + public TransactionScopeManager getTransactionScopeManager() { + return transactionScopeManager; + } + + public CQueryEngine getCQueryEngine() { + return cQueryEngine; + } + + public ClusterManager getClusterManager() { + return clusterManager; + } + + public DebugLazyLoad getDebugLazyLoad() { + return debugLazyLoad; + } + + public SpiBackgroundExecutor getBackgroundExecutor() { + return backgroundExecutor; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/JndiDataSourceLookup.java b/src/main/java/com/avaje/ebeaninternal/server/core/JndiDataSourceLookup.java new file mode 100644 index 000000000..0f814f216 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/JndiDataSourceLookup.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import com.avaje.ebean.config.GlobalProperties; + + +/** + * Helper to lookup a DataSource from JNDI. + */ +public class JndiDataSourceLookup { + + private static final String DEFAULT_PREFIX = "java:comp/env/jdbc/"; + + String jndiPrefix = GlobalProperties.get("ebean.datasource.jndi.prefix", DEFAULT_PREFIX); + + public JndiDataSourceLookup() { + } + + /** + * Return the DataSource by JNDI lookup. + *

+ * If name is null the 'default' dataSource is returned. + *

+ */ + public DataSource lookup(String jndiName) { + + try { + + if (!jndiName.startsWith("java:")){ + jndiName = jndiPrefix + jndiName; + } + + Context ctx = new InitialContext(); + DataSource ds = (DataSource) ctx.lookup(jndiName); + if (ds == null) { + throw new PersistenceException("JNDI DataSource [" + jndiName + "] not found?"); + } + return ds; + + } catch (NamingException ex) { + throw new PersistenceException(ex); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/Message.java b/src/main/java/com/avaje/ebeaninternal/server/core/Message.java new file mode 100644 index 000000000..edaea3145 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/Message.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * Utility object used for internationalising log messages. + */ +public class Message { + + private static final String bundle = "com.avaje.ebeaninternal.api.message"; + + /** + * Return a message that has a single argument. + */ + public static String msg(String key, Object arg) { + Object[] args = new Object[1]; + args[0] = arg; + return MessageFormat.format(getPattern(key), args); + } + + /** + * Return a message that has a two arguments. + */ + public static String msg(String key, Object arg, Object arg2) { + Object[] args = new Object[2]; + args[0] = arg; + args[1] = arg2; + return MessageFormat.format(getPattern(key), args); + } + + public static String msg(String key, Object arg, Object arg2, Object arg3) { + Object[] args = new Object[3]; + args[0] = arg; + args[1] = arg2; + args[2] = arg3; + return MessageFormat.format(getPattern(key), args); + } + + /** + * Return a message that has an array of arguments. + */ + public static String msg(String key, Object[] args) { + return MessageFormat.format(getPattern(key), args); + } + + /** + * Return a message that has a no arguments. + */ + public static String msg(String key) { + return MessageFormat.format(getPattern(key), new Object[0]); + } + + private static String getPattern(String key) { + try { + ResourceBundle myResources = ResourceBundle.getBundle(bundle); + return myResources.getString(key); + } catch (MissingResourceException e) { + return "MissingResource " + bundle + ":" + key; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OnBootupClassSearchMatcher.java b/src/main/java/com/avaje/ebeaninternal/server/core/OnBootupClassSearchMatcher.java new file mode 100644 index 000000000..1946be9fb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OnBootupClassSearchMatcher.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher; + +/** + * Matcher used for searching for Embeddable, Entity and ScalarTypes in the + * class path. + */ +public class OnBootupClassSearchMatcher implements ClassPathSearchMatcher { + + BootupClasses classes = new BootupClasses(); + + public boolean isMatch(Class cls) { + + return classes.isMatch(cls); + } + + public BootupClasses getOnBootupClasses() { + return classes; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java new file mode 100644 index 000000000..c6406a70b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebeaninternal.api.BeanIdList; + +/** + * The Object Relational query execution API. + */ +public interface OrmQueryEngine { + + /** + * Execute the 'find by id' query returning a single bean. + */ + public T findId(OrmQueryRequest request); + + /** + * Execute the findList, findSet, findMap query returning an appropriate BeanCollection. + */ + public BeanCollection findMany(OrmQueryRequest request); + + /** + * Execute the query using a QueryIterator. + */ + public QueryIterator findIterate(OrmQueryRequest request); + + /** + * Execute the row count query. + */ + public int findRowCount(OrmQueryRequest request); + + /** + * Execute the find id's query. + */ + public BeanIdList findIds(OrmQueryRequest request); + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java new file mode 100644 index 000000000..96b075353 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -0,0 +1,429 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryResultVisitor; +import com.avaje.ebean.RawSql; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.LoadContext; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Type; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DeployParser; +import com.avaje.ebeaninternal.server.deploy.DeployPropertyParserMap; +import com.avaje.ebeaninternal.server.loadcontext.DLoadContext; +import com.avaje.ebeaninternal.server.query.CQueryPlan; +import com.avaje.ebeaninternal.server.query.CancelableQuery; + +/** + * Wraps the objects involved in executing a Query. + */ +public final class OrmQueryRequest extends BeanRequest implements BeanQueryRequest, SpiOrmQueryRequest { + + private final BeanDescriptor beanDescriptor; + + private final OrmQueryEngine queryEngine; + + private final SpiQuery query; + + private final boolean vanillaMode; + + private final BeanFinder finder; + + private final LoadContext graphContext; + + private final Boolean readOnly; + + private final RawSql rawSql; + + private PersistenceContext persistenceContext; + + private Integer cacheKey; + + private int queryPlanHash; + + /** + * Flag set if background fetching taking place. In this case the transaction + * is rolled back by the background fetching thread. Background fetching + * always takes place in its own transaction. + */ + private boolean backgroundFetching; + + /** + * Create the InternalQueryRequest. + */ + public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery query, BeanDescriptor desc, SpiTransaction t) { + + super(server, t); + + this.beanDescriptor = desc; + this.rawSql = query.getRawSql(); + this.finder = beanDescriptor.getBeanFinder(); + this.queryEngine = queryEngine; + this.query = query; + this.vanillaMode = query.isVanillaMode(server.isVanillaMode()); + this.readOnly = query.isReadOnly(); + + this.graphContext = new DLoadContext(ebeanServer, beanDescriptor, readOnly, query); + graphContext.registerSecondaryQueries(query); + } + + public void setTotalHits(int totalHits) { + query.setTotalHits(totalHits); + } + + public void executeSecondaryQueries(int defaultQueryBatch) { + graphContext.executeSecondaryQueries(this, defaultQueryBatch); + } + + /** + * For use with QueryIterator and secondary queries this returns the minimum + * batch size that should be loaded before executing the secondary queries. + *

+ * If -1 is returned then NO secondary queries are registered and simple + * iteration is fine. + *

+ */ + public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) { + return graphContext.getSecondaryQueriesMinBatchSize(this, defaultQueryBatch); + } + + /** + * Return the Normal, sharedInstance, ReadOnly state of this query. + */ + public Boolean isReadOnly() { + return readOnly; + } + + /** + * Return the BeanDescriptor for the associated bean. + */ + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + /** + * Return the graph context for this query. + */ + public LoadContext getGraphContext() { + return graphContext; + } + + /** + * Calculate the query plan hash AFTER any potential AutoFetch tuning. + */ + public void calculateQueryPlanHash() { + this.queryPlanHash = query.queryPlanHash(this); + } + + public boolean isRawSql() { + return rawSql != null; + } + + public DeployParser createDeployParser() { + if (rawSql != null) { + return new DeployPropertyParserMap(rawSql.getColumnMapping().getMapping()); + } else { + return beanDescriptor.createDeployPropertyParser(); + } + } + + /** + * Return true if this is a query using generated sql. If false this query + * will use raw sql (Entity bean based on raw sql select). + */ + public boolean isSqlSelect() { + return query.isSqlSelect() && query.getRawSql() == null; + } + + /** + * Return the PersistenceContext used for this request. + */ + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + /** + * This will create a local (readOnly) transaction if no current transaction + * exists. + *

+ * A transaction may have been passed in explicitly or currently be active in + * the thread local. If not, then a readOnly transaction is created to execute + * this query. + *

+ */ + @Override + public void initTransIfRequired() { + // first check if the query requires its own transaction + if (query.createOwnTransaction()) { + // using background fetch or query listener etc + transaction = ebeanServer.createQueryTransaction(); + createdTransaction = true; + + } else if (transaction == null) { + // maybe a current one + transaction = ebeanServer.getCurrentServerTransaction(); + if (transaction == null) { + // create an implicit transaction to execute this query + transaction = ebeanServer.createQueryTransaction(); + createdTransaction = true; + } + } + this.persistenceContext = getPersistenceContext(query, transaction); + this.graphContext.setPersistenceContext(persistenceContext); + } + + /** + * Get the TransactionContext either explicitly set on the query or + * transaction scoped. + */ + private PersistenceContext getPersistenceContext(SpiQuery query, SpiTransaction t) { + + PersistenceContext ctx = query.getPersistenceContext(); + if (ctx == null) { + ctx = t.getPersistenceContext(); + } + return ctx; + } + + /** + * Will end a locally created transaction. + *

+ * It ends the transaction by using a rollback() as the transaction is known + * to be readOnly. + *

+ */ + public void endTransIfRequired() { + if (createdTransaction && !backgroundFetching) { + // we can rollback as readOnly transaction + transaction.rollback(); + } + } + + /** + * This query is using background fetching. + */ + public void setBackgroundFetching() { + backgroundFetching = true; + } + + /** + * Return true if this is a find by id (rather than List Set or Map). + */ + public boolean isFindById() { + return query.getType() == Type.BEAN; + } + + public boolean isVanillaMode() { + return vanillaMode; + } + + /** + * Execute the query as findById. + */ + public Object findId() { + return queryEngine.findId(this); + } + + public int findRowCount() { + return queryEngine.findRowCount(this); + } + + public List findIds() { + BeanIdList idList = queryEngine.findIds(this); + return idList.getIdList(); + } + + public void findVisit(QueryResultVisitor visitor) { + QueryIterator it = queryEngine.findIterate(this); + try { + while (it.hasNext()) { + if (!visitor.accept(it.next())) { + break; + } + } + } finally { + it.close(); + } + } + + public QueryIterator findIterate() { + return queryEngine.findIterate(this); + } + + /** + * Execute the query as findList. + */ + @SuppressWarnings("unchecked") + public List findList() { + BeanCollection bc = queryEngine.findMany(this); + return (List) (vanillaMode ? bc.getActualCollection() : bc); + } + + /** + * Execute the query as findSet. + */ + @SuppressWarnings("unchecked") + public Set findSet() { + BeanCollection bc = queryEngine.findMany(this); + return (Set) (vanillaMode ? bc.getActualCollection() : bc); + } + + /** + * Execute the query as findMap. + */ + public Map findMap() { + String mapKey = query.getMapKey(); + if (mapKey == null) { + BeanProperty[] ids = beanDescriptor.propertiesId(); + if (ids.length == 1) { + query.setMapKey(ids[0].getName()); + } else { + String msg = "No mapKey specified for query"; + throw new PersistenceException(msg); + } + } + BeanCollection bc = queryEngine.findMany(this); + return (Map) (vanillaMode ? bc.getActualCollection() : bc); + } + + public SpiQuery.Type getQueryType() { + return query.getType(); + } + + /** + * Return a bean specific finder if one has been set. + */ + public BeanFinder getBeanFinder() { + return finder; + } + + /** + * Return the find that is to be performed. + */ + public SpiQuery getQuery() { + return query; + } + + /** + * Return the many property that is fetched in the query or null if there is + * not one. + */ + public BeanPropertyAssocMany getManyProperty() { + return beanDescriptor.getManyProperty(query); + } + + /** + * Return a queryPlan for the current query if one exists. Returns null if no + * query plan for this query exists. + */ + public CQueryPlan getQueryPlan() { + return beanDescriptor.getQueryPlan(queryPlanHash); + } + + /** + * Return the queryPlanHash. + *

+ * This identifies the query plan for a given bean type. It effectively + * matches a SQL statement with ? bind variables. A query plan can be reused + * with just the bind variables changing. + *

+ */ + public int getQueryPlanHash() { + return queryPlanHash; + } + + /** + * Put the QueryPlan into the cache. + */ + public void putQueryPlan(CQueryPlan queryPlan) { + beanDescriptor.putQueryPlan(queryPlanHash, queryPlan); + } + + public boolean isUseBeanCache() { + return beanDescriptor.calculateUseCache(query.isUseBeanCache()); + } + + /** + * Try to get the query result from the query cache. + */ + public BeanCollection getFromQueryCache() { + + if (!query.isUseQueryCache()) { + return null; + } + + if (query.getType() == null) { + // the query plan and bind values must be the same + cacheKey = Integer.valueOf(query.queryHash()); + + } else { + // additionally the return type (List/Set/Map) must be the same + cacheKey = Integer.valueOf(31 * query.queryHash() + query.getType().hashCode()); + } + + // TODO: Sort out returning BeanCollection from L2 cache + return null; + + // BeanCollection bc = beanDescriptor.queryCacheGet(cacheKey); + // if (bc != null && Boolean.FALSE.equals(query.isReadOnly())) { + // // Explicit readOnly=false for query cache + // CopyContext ctx = new CopyContext(vanillaMode, false); + // return new CopyBeanCollection(bc, beanDescriptor, ctx, 5).copy(); + // } + // return bc; + } + + public void putToQueryCache(BeanCollection queryResult) { + beanDescriptor.queryCachePut(cacheKey, queryResult); + } + + /** + * Set an Query object that owns the PreparedStatement that can be cancelled. + */ + public void setCancelableQuery(CancelableQuery cancelableQuery) { + query.setCancelableQuery(cancelableQuery); + } + + /** + * Log the SQL if the logLevel is appropriate. + */ + public void logSql(String sql) { + if (transaction.isLogSql()) { + transaction.logInternal(sql); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java new file mode 100644 index 000000000..2ce3014d3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java @@ -0,0 +1,127 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.persist.BatchControl; +import com.avaje.ebeaninternal.server.persist.BatchPostExecute; +import com.avaje.ebeaninternal.server.persist.PersistExecute; + +/** + * Wraps all the objects used to persist a bean. + */ +public abstract class PersistRequest extends BeanRequest implements BatchPostExecute { + + public enum Type { + INSERT, UPDATE, DELETE, ORMUPDATE, UPDATESQL, CALLABLESQL + }; + + boolean persistCascade; + + /** + * One of INSERT, UPDATE, DELETE, UPDATESQL or CALLABLESQL. + */ + Type type; + + final PersistExecute persistExecute; + + /** + * Used by CallableSqlRequest and UpdateSqlRequest. + */ + public PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) { + super(server, t); + this.persistExecute = persistExecute; + } + + /** + * Execute a the request or queue/batch it for later execution. + */ + public abstract int executeOrQueue(); + + /** + * Execute the request right now. + */ + public abstract int executeNow(); + + public PstmtBatch getPstmtBatch() { + return ebeanServer.getPstmtBatch(); + } + + public boolean isLogSql() { + return transaction.isLogSql(); + } + + public boolean isLogSummary() { + return transaction.isLogSummary(); + } + + /** + * Execute the Callable statement. + */ + public int executeStatement() { + + boolean batch = transaction.isBatchThisRequest(); + + int rows; + BatchControl control = transaction.getBatchControl(); + if (control != null) { + rows = control.executeStatementOrBatch(this, batch); + + } else if (batch) { + // need to create the BatchControl + control = persistExecute.createBatchControl(transaction); + rows = control.executeStatementOrBatch(this, batch); + } else { + rows = executeNow(); + } + + return rows; + } + + public void initTransIfRequired() { + createImplicitTransIfRequired(false); + persistCascade = transaction.isPersistCascade(); + } + + /** + * Return the type of this request. One of INSERT, UPDATE, DELETE, UPDATESQL + * or CALLABLESQL. + */ + public Type getType() { + return type; + } + + /** + * Set the type of this request. One of INSERT, UPDATE, DELETE, UPDATESQL or + * CALLABLESQL. + */ + public void setType(Type type) { + this.type = type; + } + + /** + * Return true if save and delete should cascade. + */ + public boolean isPersistCascade() { + return persistCascade; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java new file mode 100644 index 000000000..61c67ea6a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -0,0 +1,708 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +import javax.persistence.OptimisticLockException; + +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.ValidationException; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebean.event.BeanPersistRequest; +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.TransactionEvent; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanManager; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.BatchControl; +import com.avaje.ebeaninternal.server.persist.PersistExecute; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; +import com.avaje.ebeaninternal.server.transaction.BeanDelta; +import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap; + +/** + * PersistRequest for insert update or delete of a bean. + */ +public class PersistRequestBean extends PersistRequest implements BeanPersistRequest { + + protected final BeanManager beanManager; + + protected final BeanDescriptor beanDescriptor; + + protected final BeanPersistListener beanPersistListener; + + /** + * For per post insert update delete control. + */ + protected final BeanPersistController controller; + + /** + * The associated intercept. + */ + protected final EntityBeanIntercept intercept; + + /** + * The parent bean for unidirectional save. + */ + protected final Object parentBean; + + protected final boolean isDirty; + + /** + * True if this is a vanilla bean. + */ + protected final boolean vanilla; + + /** + * The bean being persisted. + */ + protected final T bean; + + /** + * Old values used for concurrency checking. + */ + protected T oldValues; + + /** + * The concurrency mode used for update or delete. + */ + protected ConcurrencyMode concurrencyMode; + + protected final Set loadedProps; + + /** + * The unique id used for logging summary. + */ + protected Object idValue; + + /** + * Hash value used to handle cascade delete both ways in a relationship. + */ + protected Integer beanHash; + protected Integer beanIdentityHash; + + protected final Set changedProps; + + protected boolean notifyCache; + + private boolean statelessUpdate; + private boolean deleteMissingChildren; + private boolean updateNullProperties; + + /** + * Used for forced update of a bean. + */ + public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, SpiTransaction t, + PersistExecute persistExecute, Set updateProps, ConcurrencyMode concurrencyMode) { + + super(server, t, persistExecute); + this.beanManager = mgr; + this.beanDescriptor = mgr.getBeanDescriptor(); + this.beanPersistListener = beanDescriptor.getPersistListener(); + this.bean = bean; + this.parentBean = parentBean; + + this.controller = beanDescriptor.getPersistController(); + this.concurrencyMode = beanDescriptor.getConcurrencyMode(); + + this.concurrencyMode = concurrencyMode; + this.loadedProps = updateProps; + this.changedProps = updateProps; + + this.vanilla = true; + this.isDirty = true; + this.oldValues = bean; + if (bean instanceof EntityBean) { + this.intercept = ((EntityBean) bean)._ebean_getIntercept(); + } else { + this.intercept = null; + } + } + + @SuppressWarnings("unchecked") + public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, + SpiTransaction t, PersistExecute persistExecute) { + + super(server, t, persistExecute); + this.beanManager = mgr; + this.beanDescriptor = mgr.getBeanDescriptor(); + this.beanPersistListener = beanDescriptor.getPersistListener(); + this.bean = bean; + this.parentBean = parentBean; + + this.controller = beanDescriptor.getPersistController(); + this.concurrencyMode = beanDescriptor.getConcurrencyMode(); + + if (bean instanceof EntityBean) { + this.intercept = ((EntityBean) bean)._ebean_getIntercept(); + if (intercept.isReference()) { + // allowed to delete reference objects + // with no concurrency checking + this.concurrencyMode = ConcurrencyMode.NONE; + } + // this is ok to not use isNewOrDirty() as used for updates only + this.isDirty = intercept.isDirty(); + if (!isDirty) { + this.changedProps = intercept.getChangedProps(); + } else { + // merge changed properties on the bean with changed embedded beans + Set beanChangedProps = intercept.getChangedProps(); + Set dirtyEmbedded = beanDescriptor.getDirtyEmbeddedProperties(bean); + this.changedProps = mergeChangedProperties(beanChangedProps, dirtyEmbedded); + } + this.loadedProps = intercept.getLoadedProps(); + this.oldValues = (T) intercept.getOldValues(); + this.vanilla = false; + + } else { + // have to assume the vanilla bean is dirty + this.vanilla = true; + this.isDirty = true; + this.loadedProps = null; + this.changedProps = null; + this.intercept = null; + + // degrade concurrency checking to none for vanilla bean + if (concurrencyMode.equals(ConcurrencyMode.ALL)) { + this.concurrencyMode = ConcurrencyMode.NONE; + } + } + } + + /** + * Merge the changed properties for the bean and embedded beans. + */ + private Set mergeChangedProperties(Set beanChangedProps, Set embChanged) { + if (embChanged == null) { + return beanChangedProps; + } else if (beanChangedProps == null) { + return embChanged; + } else { + beanChangedProps.addAll(embChanged); + return beanChangedProps; + } + } + + public boolean isNotify(TransactionEvent txnEvent) { + return notifyCache || isNotifyPersistListener(); + } + + public boolean isNotifyCache() { + return notifyCache; + } + + public boolean isNotifyPersistListener() { + return beanPersistListener != null; + } + + public void notifyCache() { + if (notifyCache) { + switch (type) { + case INSERT: + beanDescriptor.cacheInsert(idValue, this); + break; + case UPDATE: + beanDescriptor.cacheUpdate(idValue, this); + break; + case DELETE: + beanDescriptor.cacheDelete(idValue, this); + break; + default: + throw new IllegalStateException("Invalid type "+type); + } + } + } + + public void addToPersistMap(BeanPersistIdMap beanPersistMap) { + + beanPersistMap.add(beanDescriptor, type, idValue); + } + + public boolean notifyLocalPersistListener() { + if (beanPersistListener == null) { + return false; + + } else { + switch (type) { + case INSERT: + return beanPersistListener.inserted(bean); + + case UPDATE: + return beanPersistListener.updated(bean, getUpdatedProperties()); + + case DELETE: + return beanPersistListener.deleted(bean); + + default: + return false; + } + } + } + + public boolean isParent(Object o) { + return o == parentBean; + } + + /** + * Return true if this bean has been already been persisted + * (inserted/updated or deleted) in this transaction. + */ + public boolean isRegisteredBean() { + return transaction.isRegisteredBean(bean); + } + + public void unRegisterBean() { + transaction.unregisterBean(bean); + } + + /** + * The hash used to register the bean with the transaction. + *

+ * Takes into account the class type and id value. + *

+ */ + private Integer getBeanHash() { + if (beanHash == null) { + Object id = beanDescriptor.getId(bean); + int hc = 31 * bean.getClass().getName().hashCode(); + if (id != null) { + hc += id.hashCode(); + } + beanHash = Integer.valueOf(hc); + } + return beanHash; + } + + public void registerDeleteBean() { + Integer hash = getBeanHash(); + transaction.registerDeleteBean(hash); + } + + public void unregisterDeleteBean() { + Integer hash = getBeanHash(); + transaction.unregisterDeleteBean(hash); + } + + public boolean isRegisteredForDeleteBean() { + if (transaction == null){ + return false; + } else { + Integer hash = getBeanHash(); + return transaction.isRegisteredDeleteBean(hash); + } + } + + /** + * Set the type of this request. One of INSERT, UPDATE, DELETE, UPDATESQL or + * CALLABLESQL. + */ + @Override + public void setType(Type type) { + this.type = type; + notifyCache = beanDescriptor.isCacheNotify(); + if (type == Type.DELETE || type == Type.UPDATE) { + if (oldValues == null) { + oldValues = bean; + } + } + } + + public BeanManager getBeanManager() { + return beanManager; + } + + /** + * Return the BeanDescriptor for the associated bean. + */ + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + /** + * Return true if this is a stateless update. + */ + public boolean isStatelessUpdate() { + return statelessUpdate; + } + + /** + * Return true if a stateless update should also delete any missing details + * beans. + */ + public boolean isDeleteMissingChildren() { + return deleteMissingChildren; + } + + /** + * Return true if null properties should be updated (treated as loaded) for + * stateless updates. + */ + public boolean isUpdateNullProperties() { + return updateNullProperties; + } + + /** + * Set to true if this is a stateless update. + *

+ * By Stateless it means that the bean was not previously fetched (and so + * does not have it's previous state) so we are doing an update on a bean + * that was probably created from JSON or XML. + *

+ */ + public void setStatelessUpdate(boolean statelessUpdate, boolean deleteMissingChildren, boolean updateNullProperties) { + this.statelessUpdate = statelessUpdate; + this.deleteMissingChildren = deleteMissingChildren; + this.updateNullProperties = updateNullProperties; + } + + /** + * Used to skip updates if we know the bean is not dirty. This is the case + * for EntityBeans that have not been modified. + */ + public boolean isDirty() { + return isDirty; + } + + /** + * Return the concurrency mode used for this persist. + */ + public ConcurrencyMode getConcurrencyMode() { + return concurrencyMode; + } + + /** + * Set loaded properties when generated values has added properties such as + * created and updated timestamps. + */ + public void setLoadedProps(Set additionalProps) { + if (intercept != null) { + intercept.setLoadedProps(additionalProps); + } + } + + public Set getLoadedProperties() { + return loadedProps; + } + + /** + * Returns a description of the request. This is typically the bean class + * name or the base table for MapBeans. + *

+ * Used to determine common persist requests for queueing and statement + * batching. + *

+ */ + public String getFullName() { + return beanDescriptor.getFullName(); + } + + /** + * Return the bean associated with this request. + */ + public T getBean() { + return bean; + } + + /** + * Return the Id value for the bean. + */ + public Object getBeanId() { + return beanDescriptor.getId(bean); + } + + public BeanDelta createDeltaBean() { + return new BeanDelta(beanDescriptor, getBeanId()); + } + + /** + * Get the old values bean. This is used to perform optimistic concurrency + * checking on updates and deletes. + */ + public T getOldValues() { + return oldValues; + } + + /** + * Return the parent bean for cascading save with unidirectional + * relationship. + */ + public Object getParentBean() { + return parentBean; + } + + /** + * Return the controller if there is one associated with this type of bean. + * This returns null if there is no controller associated. + */ + public BeanPersistController getBeanController() { + return controller; + } + + /** + * Return the intercept if there is one. + */ + public EntityBeanIntercept getEntityBeanIntercept() { + return intercept; + } + + /** + * Validate the bean. This is not recursive and only runs the 'local' + * validation rules. + */ + public void validate() { + InvalidValue errs = beanDescriptor.validate(false, bean); + if (errs != null) { + throw new ValidationException(errs); + } + } + + /** + * Return true if this property is loaded (full bean or included in partial + * bean). + */ + public boolean isLoadedProperty(BeanProperty prop) { + if (loadedProps == null) { + return true; + } else { + return loadedProps.contains(prop.getName()); + } + } + + @Override + public int executeNow() { + switch (type) { + case INSERT: + persistExecute.executeInsertBean(this); + return -1; + + case UPDATE: + persistExecute.executeUpdateBean(this); + return -1; + + case DELETE: + persistExecute.executeDeleteBean(this); + return -1; + + default: + throw new RuntimeException("Invalid type " + type); + } + } + + @Override + public int executeOrQueue() { + + boolean batch = transaction.isBatchThisRequest(); + + BatchControl control = transaction.getBatchControl(); + if (control != null) { + return control.executeOrQueue(this, batch); + } + if (batch) { + control = persistExecute.createBatchControl(transaction); + return control.executeOrQueue(this, batch); + + } else { + return executeNow(); + } + } + + /** + * Set the generated key back to the bean. Only used for inserts with + * getGeneratedKeys. + */ + public void setGeneratedKey(Object idValue) { + if (idValue != null) { + + // set back to the bean so that we can use the same bean later + // for update [refer ebeanIntercept.setLoaded(true)]. + idValue = beanDescriptor.convertSetId(idValue, bean); + + // remember it for logging summary + this.idValue = idValue; + } + } + + /** + * Set the Id value that was bound. Used for the purposes of logging summary + * information on this request. + */ + public void setBoundId(Object idValue) { + this.idValue = idValue; + } + + /** + * Check for optimistic concurrency exception. + */ + public final void checkRowCount(int rowCount) throws SQLException { + if (rowCount != 1) { + String m = Message.msg("persist.conc2", "" + rowCount); + throw new OptimisticLockException(m, null, bean); + } + } + + /** + * Post processing. + */ + public void postExecute() throws SQLException { + + if (controller != null) { + controllerPost(); + } + + if (intercept != null) { + // if bean persisted again then should result in an update + intercept.setLoaded(); + } + + addEvent(); + + if (isLogSummary()) { + logSummary(); + } + } + + private void controllerPost() { + switch (type) { + case INSERT: + controller.postInsert(this); + break; + case UPDATE: + controller.postUpdate(this); + break; + case DELETE: + controller.postDelete(this); + break; + default: + break; + } + } + + private void logSummary() { + + String name = beanDescriptor.getName(); + switch (type) { + case INSERT: + transaction.logInternal("Inserted [" + name + "] [" + idValue + "]"); + break; + case UPDATE: + transaction.logInternal("Updated [" + name + "] [" + idValue + "]"); + break; + case DELETE: + transaction.logInternal("Deleted [" + name + "] [" + idValue + "]"); + break; + default: + break; + } + } + + /** + * Add the bean to the TransactionEvent. This will be used by + * TransactionManager to synch Cache, Cluster and text indexes. + */ + private void addEvent() { + + TransactionEvent event = transaction.getEvent(); + if (event != null) { + event.add(this); + } + } + + /** + * Determine the concurrency mode depending on fully/partially populated + * bean. + *

+ * Specifically with version concurrency we want to check that the version + * property was one of the loaded properties. + *

+ */ + public ConcurrencyMode determineConcurrencyMode() { + if (loadedProps != null) { + // 'partial bean' update/delete... + if (concurrencyMode.equals(ConcurrencyMode.VERSION)) { + // check the version property was loaded + BeanProperty prop = beanDescriptor.firstVersionProperty(); + if (prop != null && loadedProps.contains(prop.getName())) { + // OK to use version property + } else { + concurrencyMode = ConcurrencyMode.ALL; + } + } + } + return concurrencyMode; + } + + /** + * Return true if the update DML/SQL must be dynamically generated. + *

+ * This is the case for updates/deletes of partially populated beans. + *

+ */ + public boolean isDynamicUpdateSql() { + return !vanilla && beanDescriptor.isUpdateChangesOnly() || (loadedProps != null); + } + + /** + * Create a GenerateDmlRequest used to generate the DML. + *

+ * Will used changed properties or loaded properties depending on the + * BeanDescriptor.isUpdateChangesOnly() value. + *

+ */ + public GenerateDmlRequest createGenerateDmlRequest(boolean emptyStringAsNull) { + if (beanDescriptor.isUpdateChangesOnly()) { + return new GenerateDmlRequest(emptyStringAsNull, changedProps, loadedProps, oldValues); + } else { + return new GenerateDmlRequest(emptyStringAsNull, loadedProps, loadedProps, oldValues); + } + } + + /** + * Return the updated properties. If this returns null then all the + * properties on the bean where updated. + */ + public Set getUpdatedProperties() { + if (changedProps != null) { + return changedProps; + } + return loadedProps; + } + + /** + * Test if the property value has changed and if so include it in the + * update. + */ + public boolean hasChanged(BeanProperty prop) { + + return changedProps.contains(prop.getName()); + } + + public List getDerivedRelationships() { + return transaction.getDerivedRelationship(bean); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestCallableSql.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestCallableSql.java new file mode 100644 index 000000000..f949e3e92 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestCallableSql.java @@ -0,0 +1,171 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebean.CallableSql; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiCallableSql; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.TransactionEventTable; +import com.avaje.ebeaninternal.api.BindParams.Param; +import com.avaje.ebeaninternal.server.persist.PersistExecute; + +/** + * Persist request specifically for CallableSql. + */ +public final class PersistRequestCallableSql extends PersistRequest { + + private final SpiCallableSql callableSql; + + private int rowCount; + + private String bindLog; + + private CallableStatement cstmt; + + private BindParams bindParam; + + /** + * Create. + */ + public PersistRequestCallableSql(SpiEbeanServer server, + CallableSql cs, SpiTransaction t, PersistExecute persistExecute) { + + super(server, t, persistExecute); + this.type = PersistRequest.Type.CALLABLESQL; + this.callableSql = (SpiCallableSql)cs; + } + + @Override + public int executeOrQueue() { + return executeStatement(); + } + + @Override + public int executeNow() { + return persistExecute.executeSqlCallable(this); + } + + /** + * Return the CallableSql. + */ + public SpiCallableSql getCallableSql() { + return callableSql; + } + + /** + * The the log of bind values. + */ + public void setBindLog(String bindLog) { + this.bindLog = bindLog; + } + + /** + * Note the rowCount of the execution. + */ + public void checkRowCount(int count) throws SQLException { + this.rowCount = count; + } + + /** + * Only called for insert with generated keys. + */ + public void setGeneratedKey(Object idValue) { + } + + /** + * False for CallableSql. + */ + public boolean useGeneratedKeys() { + return false; + } + + /** + * Perform post execute processing for the CallableSql. + */ + public void postExecute() throws SQLException { + + if (transaction.isLogSummary()) { + String m = "CallableSql label[" + callableSql.getLabel() + "]" + " rows[" + rowCount+ "]" + " bind[" + bindLog + "]"; + transaction.logInternal(m); + } + + // register table modifications with the transaction event + TransactionEventTable tableEvents = callableSql.getTransactionEventTable(); + + if (tableEvents != null && !tableEvents.isEmpty()) { + transaction.getEvent().add(tableEvents); + } + + } + + /** + * These need to be set for use with Non-batch execution. Specifically to + * read registered out parameters and potentially handle the + * executeOverride() method. + */ + public void setBound(BindParams bindParam, CallableStatement cstmt) { + this.bindParam = bindParam; + this.cstmt = cstmt; + } + + /** + * Execute the statement in normal non batch mode. + */ + public int executeUpdate() throws SQLException { + + // check to see if the execution has been overridden + // only works in non-batch mode + if (callableSql.executeOverride(cstmt)) { + return -1; + // // been overridden so just return the rowCount + // rowCount = callableSql.getRowCount(); + // return rowCount; + } + + rowCount = cstmt.executeUpdate(); + + // only read in non-batch mode + readOutParams(); + + return rowCount; + } + + private void readOutParams() throws SQLException { + + List list = bindParam.positionedParameters(); + int pos = 0; + + for (int i = 0; i < list.size(); i++) { + pos++; + BindParams.Param param = (BindParams.Param) list.get(i); + if (param.isOutParam()) { + Object outValue = cstmt.getObject(pos); + param.setOutValue(outValue); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestOrmUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestOrmUpdate.java new file mode 100644 index 000000000..3b52c737e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestOrmUpdate.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.sql.SQLException; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiUpdate; +import com.avaje.ebeaninternal.api.SpiUpdate.OrmUpdateType; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanManager; +import com.avaje.ebeaninternal.server.persist.PersistExecute; + +/** + * Persist request specifically for CallableSql. + */ +public final class PersistRequestOrmUpdate extends PersistRequest { + + private final BeanDescriptor beanDescriptor; + + private SpiUpdate ormUpdate; + + private int rowCount; + + private String bindLog; + + /** + * Create. + */ + public PersistRequestOrmUpdate(SpiEbeanServer server, BeanManager mgr, SpiUpdate ormUpdate, + SpiTransaction t, PersistExecute persistExecute) { + + super(server, t, persistExecute); + this.beanDescriptor = mgr.getBeanDescriptor(); + this.ormUpdate = ormUpdate; + } + + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + @Override + public int executeNow() { + return persistExecute.executeOrmUpdate(this); + } + + @Override + public int executeOrQueue() { + return executeStatement(); + } + + + /** + * Return the UpdateSql. + */ + public SpiUpdate getOrmUpdate() { + return ormUpdate; + } + + /** + * No concurrency checking so just note the rowCount. + */ + public void checkRowCount(int count) throws SQLException { + this.rowCount = count; + } + + /** + * Always false. + */ + public boolean useGeneratedKeys() { + return false; + } + + /** + * Not called for this type of request. + */ + public void setGeneratedKey(Object idValue) { + } + + /** + * Set the bound values. + */ + public void setBindLog(String bindLog) { + this.bindLog = bindLog; + } + + /** + * Perform post execute processing. + */ + public void postExecute() throws SQLException { + + OrmUpdateType ormUpdateType = ormUpdate.getOrmUpdateType(); + String tableName = ormUpdate.getBaseTable(); + + if (transaction.isLogSummary()) { + String m = ormUpdateType + " table[" + tableName + "] rows["+ rowCount + "] bind[" + bindLog + "]"; + transaction.logInternal(m); + } + + if (ormUpdate.isNotifyCache()) { + + // add the modification info to the TransactionEvent + // this is used to invalidate cached objects etc + switch (ormUpdateType) { + case INSERT: + transaction.getEvent().add(tableName, true, false, false); + break; + case UPDATE: + transaction.getEvent().add(tableName, false, true, false); + break; + case DELETE: + transaction.getEvent().add(tableName, false, false, true); + break; + default: + break; + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestUpdateSql.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestUpdateSql.java new file mode 100644 index 000000000..e5a5b5882 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestUpdateSql.java @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.sql.SQLException; + +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiSqlUpdate; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.persist.PersistExecute; + +/** + * Persist request specifically for CallableSql. + */ +public final class PersistRequestUpdateSql extends PersistRequest { + + public enum SqlType { + SQL_UPDATE, SQL_DELETE, SQL_INSERT, SQL_UNKNOWN + }; + + private final SpiSqlUpdate updateSql; + + private int rowCount; + + private String bindLog; + + private SqlType sqlType; + + private String tableName; + + private String description; + + /** + * Create. + */ + public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate updateSql, + SpiTransaction t, PersistExecute persistExecute) { + super(server, t, persistExecute); + this.type = Type.UPDATESQL; + this.updateSql = (SpiSqlUpdate)updateSql; + } + + @Override + public int executeNow() { + return persistExecute.executeSqlUpdate(this); + } + + @Override + public int executeOrQueue() { + return executeStatement(); + } + + /** + * Return the UpdateSql. + */ + public SpiSqlUpdate getUpdateSql() { + return updateSql; + } + + /** + * No concurrency checking so just note the rowCount. + */ + public void checkRowCount(int count) throws SQLException { + this.rowCount = count; + } + + /** + * Always false. + */ + public boolean useGeneratedKeys() { + return false; + } + + /** + * Not called for this type of request. + */ + public void setGeneratedKey(Object idValue) { + } + + /** + * Specify the type of statement executed. Used to automatically register + * with the transaction event. + */ + public void setType(SqlType sqlType, String tableName, String description) { + this.sqlType = sqlType; + this.tableName = tableName; + this.description = description; + } + + /** + * Set the bound values. + */ + public void setBindLog(String bindLog) { + this.bindLog = bindLog; + } + + /** + * Perform post execute processing. + */ + public void postExecute() throws SQLException { + + if (transaction.isLogSummary()) { + String m = description + " table[" + tableName + "] rows["+ rowCount + "] bind[" + bindLog + "]"; + transaction.logInternal(m); + } + + if (updateSql.isAutoTableMod()) { + // add the modification info to the TransactionEvent + // this is used to invalidate cached objects etc + switch (sqlType) { + case SQL_INSERT: + transaction.getEvent().add(tableName, true, false, false); + break; + case SQL_UPDATE: + transaction.getEvent().add(tableName, false, true, false); + break; + case SQL_DELETE: + transaction.getEvent().add(tableName, false, false, true); + break; + + default: + break; + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java new file mode 100644 index 000000000..dbfdfe587 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.Collection; +import java.util.Set; + +import com.avaje.ebean.CallableSql; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.Update; + + +/** + * API for persisting a bean. + */ +public interface Persister { + + /** + * Force an Update using the given bean. + */ + public void forceUpdate(Object entityBean, Set updateProps, Transaction t, boolean deleteMissingChildren, boolean updateNullProperties); + + /** + * Force an Insert using the given bean. + */ + public void forceInsert(Object entityBean, Transaction t); + + /** + * Insert or update the bean depending on its state. + */ + public void save(Object entityBean, Transaction t); + + /** + * Save the associations of a ManyToMany given the owner bean and the + * propertyName of the ManyToMany collection. + */ + public void saveManyToManyAssociations(Object ownerBean, String propertyName, Transaction t); + + /** + * Save an association (OneToMany, ManyToOne, OneToOne or ManyToMany). + * + * @param parentBean + * the bean that owns the association. + * @param propertyName + * the name of the property to save. + * @param t + * the transaction to use. + */ + public void saveAssociation(Object parentBean, String propertyName, Transaction t); + + /** + * Delete the associations of a ManyToMany given the owner bean and the property name of the ManyToMany. + */ + public int deleteManyToManyAssociations(Object ownerBean, String propertyName, Transaction t); + + /** + * Delete a bean given it's type and id value. + *

+ * This will also cascade delete one level of children. + *

+ */ + public int delete(Class beanType, Object id, Transaction transaction); + + /** + * Delete the bean. + */ + public void delete(Object entityBean, Transaction t); + + /** + * Delete multiple beans given a collection of Id values. + */ + public void deleteMany(Class beanType, Collection ids, Transaction transaction); + + /** + * Execute the Update. + */ + public int executeOrmUpdate(Update update, Transaction t); + + /** + * Execute the UpdateSql. + */ + public int executeSqlUpdate(SqlUpdate update, Transaction t); + + /** + * Execute the CallableSql. + */ + public int executeCallable(CallableSql callable, Transaction t); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/ProxyBeanObjectInputStream.java b/src/main/java/com/avaje/ebeaninternal/server/core/ProxyBeanObjectInputStream.java new file mode 100644 index 000000000..64068d998 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/ProxyBeanObjectInputStream.java @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.bean.SerializeControl; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.subclass.SubClassUtil; + +/** + * Read an ObjectInputStream potentially containing "proxy" / "subclassed" + * entity objects. + *

+ * This does not need to be used for "Enhanced" beans... but if you want to + * deserialise "proxy" / "subclassed" beans you need to use this + * ProxyBeanObjectInputStream. The reason is because it is required to resolve + * the class (The class with the $$EntityBean suffix). As this class is in + * another class loader typically as plain ObjectInputStream is unable to resolve + * the class - and hence we need to use this ProxyBeanObjectInputStream. + *

+ */ +public class ProxyBeanObjectInputStream extends ObjectInputStream { + + private final SpiEbeanServer ebeanServer; + + /** + * Create with a given InputStream and EbeanServer. + *

+ * The EbeanServer should be the one that created the 'proxy' classes that + * were serialised. + *

+ */ + public ProxyBeanObjectInputStream(InputStream in, EbeanServer ebeanServer) + throws IOException { + + super(in); + this.ebeanServer = (SpiEbeanServer) ebeanServer; + SerializeControl.setVanilla(false); + } + + /** + * close and reset the serialization mode. + *

+ * uses SerializeControl.resetToDefault(). + *

+ */ + public void close() throws IOException { + super.close(); + SerializeControl.resetToDefault(); + } + + /** + * Resolve the generated Class potentially using reading the embedded + * MethodInfo. + */ + protected Class resolveGenerated(ObjectStreamClass desc) + throws IOException, ClassNotFoundException { + + String className = desc.getName(); + + String vanillaClassName = SubClassUtil.getSuperClassName(className); + Class vanillaClass = ClassUtil.forName(vanillaClassName, this.getClass()); + + BeanDescriptor d = ebeanServer.getBeanDescriptor(vanillaClass); + if (d == null) { + String msg = "Could not find BeanDescriptor for "+ vanillaClassName; + throw new IOException(msg); + } else { + return d.getFactoryType(); + } + } + + /** + * checks for generated subclasses and handles them appropriately. + */ + protected Class resolveClass(ObjectStreamClass desc) throws IOException, + ClassNotFoundException { + + String className = desc.getName(); + if (SubClassUtil.isSubClass(className)) { + return resolveGenerated(desc); + } + + return super.resolveClass(desc); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PstmtBatch.java b/src/main/java/com/avaje/ebeaninternal/server/core/PstmtBatch.java new file mode 100644 index 000000000..d06a6c5d9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PstmtBatch.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * If Oracle supported the JDBC api fully this would not be required. + */ +public interface PstmtBatch { + + public void setBatchSize(PreparedStatement pstmt, int batchSize); + + public void addBatch(PreparedStatement pstmt) throws SQLException; + + public int executeBatch(PreparedStatement pstmt, int expectedRows, String sql, boolean occCheck) throws SQLException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/RefreshHelp.java b/src/main/java/com/avaje/ebeaninternal/server/core/RefreshHelp.java new file mode 100644 index 000000000..1b7f9fd7b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/RefreshHelp.java @@ -0,0 +1,234 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + + +/** + * Helper for performing a 'refresh' on an Entity bean. + *

+ * Note that this does not 'refresh' any OnetoMany or ManyToMany properties. It + * refreshes all the other properties though. + *

+ */ +public class RefreshHelp { +// +// /** +// * Helper for debug of lazy loading. +// */ +// private final DebugLazyLoad debugLazyLoad; +// +// private final MAdminLoggingMBean logControl; +// +// public RefreshHelp(MAdminLoggingMBean logControl, boolean debugLazyLoad){ +// this.logControl = logControl; +// this.debugLazyLoad = new DebugLazyLoad(debugLazyLoad); +// } +// +// /** +// * Refresh the bean from property values in dbBean. +// */ +// public void refresh(Object o, Object dbBean, BeanDescriptor desc, EntityBeanIntercept ebi, Object id, boolean isLazyLoad) { +// +// Object originalOldValues = null; +// boolean setOriginalOldValues = false; +// +// // set of properties to exclude from the refresh because it is +// // not a refresh but rather a lazyLoading event. +// Set excludes = null; +// +// // turn off intercepting so lazy loading is +// // not invoked when populating the bean +// // with PropertyChangeSupport +// ebi.setIntercepting(false); +// +// boolean readOnly = ebi.isReadOnly(); +// boolean sharedInstance = ebi.isSharedInstance(); +// +// if (isLazyLoad){ +// excludes = ebi.getLoadedProps(); +// if (excludes != null){ +// // lazy loading a "Partial Object"... which already +// // contains some properties and perhaps some oldValues +// // and these will need to be maintained... +// originalOldValues = ebi.getOldValues(); +// setOriginalOldValues = originalOldValues != null; +// } +// +// if (logControl.isDebugLazyLoad()){ +// debug(desc, ebi, id, excludes); +// } +// } +// +// +// BeanProperty[] props = desc.propertiesBaseScalar(); +// for (int i = 0; i < props.length; i++) { +// BeanProperty prop = props[i]; +// if (excludes != null && excludes.contains(prop.getName())){ +// // ignore this property (partial bean lazy loading) +// +// } else { +// Object dbVal = prop.getValue(dbBean); +// if (isLazyLoad) { +// prop.setValue(o, dbVal); +// } else { +// prop.setValueIntercept(o, dbVal); +// } +// if (setOriginalOldValues){ +// // maintain original oldValues for partially loaded bean +// prop.setValue(originalOldValues, dbVal); +// } +// } +// } +// +// BeanPropertyAssocOne[] ones = desc.propertiesOne(); +// for (int i = 0; i < ones.length; i++) { +// BeanProperty prop = ones[i]; +// if (excludes != null && excludes.contains(prop.getName())){ +// // ignore this property (partial bean lazy loading) +// +// } else { +// Object dbVal = prop.getValue(dbBean); +// if (isLazyLoad){ +// prop.setValue(o, dbVal); +// } else { +// prop.setValueIntercept(o, dbVal); +// } +// if (setOriginalOldValues){ +// // maintain original oldValues for partially loaded bean +// prop.setValue(originalOldValues, dbVal); +// } +// if (dbVal != null){ +// if (sharedInstance){ +// // propagate sharedInstance status to associated beans +// ((EntityBean)dbVal)._ebean_getIntercept().setSharedInstance(); +// } else if (readOnly) { +// // propagate readOnly status to associated beans +// ((EntityBean)dbVal)._ebean_getIntercept().setReadOnly(true); +// } +// } +// +// } +// } +// +// refreshEmbedded(o, dbBean, desc, excludes, readOnly); +// +// // set a lazy loading many proxy if required +// BeanPropertyAssocMany[] manys = desc.propertiesMany(); +// for (int i = 0; i < manys.length; i++) { +// BeanPropertyAssocMany prop = manys[i]; +// if (excludes != null && excludes.contains(prop.getName())){ +// // the many already existed on the bean +// +// } else { +// // set a lazy loading proxy +// prop.createReference(o, null, readOnly, sharedInstance); +// } +// } +// +// // the refreshed/lazy loaded bean is always fully +// // populated so set loadedProps to null +// ebi.setLoadedProps(null); +// +// +// // reset the loaded status +// ebi.setLoaded(); +// } +// +// /** +// * Refresh the Embedded beans. +// */ +// private void refreshEmbedded(Object o, Object dbBean, BeanDescriptor desc, Set excludes, boolean propagateReadOnly) { +// +// BeanPropertyAssocOne[] embeds = desc.propertiesEmbedded(); +// for (int i = 0; i < embeds.length; i++) { +// BeanPropertyAssocOne prop = embeds[i]; +// if (excludes != null && excludes.contains(prop.getName())){ +// // ignore this property +// } else { +// // the original embedded bean +// Object oEmb = prop.getValue(o); +// +// // the new one from the database +// Object dbEmb = prop.getValue(dbBean); +// +// if (oEmb == null){ +// // original embedded bean was null +// // so just replace the entire embedded bean +// prop.setValueIntercept(o, dbEmb); +// if (propagateReadOnly && dbEmb != null){ +// // propagate readOnly status to embedded beans +// ((EntityBean)dbEmb)._ebean_getIntercept().setReadOnly(true); +// } +// +// } else { +// // refresh each property of the original +// // embedded bean +// if (oEmb instanceof EntityBean){ +// // turn off interception to stop invoking lazy loading +// // but allow PropertyChangeSupport +// ((EntityBean) oEmb)._ebean_getIntercept().setIntercepting(false); +// } +// +// BeanProperty[] props = prop.getProperties(); +// for (int j = 0; j < props.length; j++) { +// Object v = props[j].getValue(dbEmb); +// props[j].setValueIntercept(oEmb, v); +// } +// +// // No longer calling setLoaded() on embedded bean +// // as the EntityBean itself +// // .. calls setEmbeddedLoaded() on each of +// // .. its embedded beans itself. +// } +// } +// } +// } +// +// +// /** +// * Output some debug to describe the lazy loading event. +// */ +// private void debug(BeanDescriptor desc, EntityBeanIntercept ebi, Object id, Set excludes) { +// +// +// Class beanType = desc.getBeanType(); +// +// StackTraceElement cause = debugLazyLoad.getStackTraceElement(beanType); +// +// String lazyLoadProperty = ebi.getLazyLoadProperty(); +// String msg = "debug.lazyLoad ["+desc+"] id["+id+"] lazyLoadProperty["+lazyLoadProperty+"]"; +// if (excludes != null){ +// msg += " partialProps"+excludes; +// } +// if (cause != null){ +// String causeLine = cause.toString(); +// if (causeLine.indexOf(".groovy:") > -1){ +// // eclipse console does not like finding groovy source at the moment +// causeLine = StringHelper.replaceString(causeLine, ".groovy:", ".groovy :"); +// } +// msg += " at: "+causeLine; +// } +// System.err.println(msg); +// } +// +// +// + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryEngine.java new file mode 100644 index 000000000..e607fa9a1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryEngine.java @@ -0,0 +1,11 @@ +package com.avaje.ebeaninternal.server.core; + + +public interface RelationalQueryEngine { + + /** + * Find a list of beans using relational query. + */ + public abstract Object findMany(RelationalQueryRequest request); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java new file mode 100644 index 000000000..27ce0145a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java @@ -0,0 +1,147 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiSqlQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; + +/** + * Wraps the objects involved in executing a SqlQuery. + */ +public final class RelationalQueryRequest { + + private final SpiSqlQuery query; + + private final RelationalQueryEngine queryEngine; + + private final SpiEbeanServer ebeanServer; + + private SpiTransaction trans; + + private boolean createdTransaction; + + private SpiQuery.Type queryType; + + /** + * Create the BeanFindRequest. + */ + public RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) { + this.ebeanServer = server; + this.queryEngine = engine; + this.query = (SpiSqlQuery) q; + this.trans = (SpiTransaction) t; + } + + /** + * Rollback the transaction if it was created for this request. + */ + public void rollbackTransIfRequired() { + if (createdTransaction) { + trans.rollback(); + } + } + + /** + * Create a transaction if none currently exists. + */ + public void initTransIfRequired() { + if (trans == null) { + trans = ebeanServer.getCurrentServerTransaction(); + if (trans == null || !trans.isActive()) { + // create a local readOnly transaction + trans = ebeanServer.createServerTransaction(false, -1); + + // commented out for performance reasons... + // TODO: review performance of trans.setReadOnly(true) + // trans.setReadOnly(true); + createdTransaction = true; + } + } + } + + /** + * End the transaction if it was locally created. + */ + public void endTransIfRequired() { + if (createdTransaction) { + // we can rollback as a readOnly transaction. + trans.rollback(); + } + } + + @SuppressWarnings("unchecked") + public List findList() { + queryType = SpiQuery.Type.LIST; + return (List) queryEngine.findMany(this); + } + + @SuppressWarnings("unchecked") + public Set findSet() { + queryType = SpiQuery.Type.SET; + return (Set) queryEngine.findMany(this); + } + + @SuppressWarnings("unchecked") + public Map findMap() { + queryType = SpiQuery.Type.MAP; + return (Map) queryEngine.findMany(this); + } + + /** + * Return the find that is to be performed. + */ + public SpiSqlQuery getQuery() { + return query; + } + + /** + * Return the type (List, Set or Map) that this fetch returns. + */ + public SpiQuery.Type getQueryType() { + return queryType; + } + + public EbeanServer getEbeanServer() { + return ebeanServer; + } + + public SpiTransaction getTransaction() { + return trans; + } + + public boolean isLogSql() { + return trans.isLogSql(); + } + + public boolean isLogSummary() { + return trans.isLogSummary(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/ServletContextListener.java b/src/main/java/com/avaje/ebeaninternal/server/core/ServletContextListener.java new file mode 100644 index 000000000..3ed885a59 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/ServletContextListener.java @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.logging.Logger; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.server.lib.ShutdownManager; + +/** + * Listens for webserver server starting and stopping events. + * + *

+ * Register this listener in the web.xml configuration file. This will listen + * for startup and shutdown events. + *

+ */ +public class ServletContextListener implements javax.servlet.ServletContextListener { + + private static final Logger logger = Logger.getLogger(ServletContextListener.class.getName()); + + /** + * The servlet container is stopping. + */ + public void contextDestroyed(ServletContextEvent event) { + ShutdownManager.shutdown(); + } + + /** + * The servlet container is starting. + *

+ * Initialise the properties file using SystemProperties.initWebapp(); + * and start Ebean. + *

+ */ + public void contextInitialized(ServletContextEvent event) { + + try { + ServletContext servletContext = event.getServletContext(); + GlobalProperties.setServletContext(servletContext); + + if (servletContext != null) { + String servletRealPath = servletContext.getRealPath(""); + GlobalProperties.put("servlet.realpath", servletRealPath); + logger.info("servlet.realpath=[" + servletRealPath + "]"); + } + + Ebean.getServer(null); + + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java new file mode 100644 index 000000000..d923647d1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryResultVisitor; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Defines the ORM query request api. + */ +public interface SpiOrmQueryRequest { + + /** + * Return the query. + */ + public SpiQuery getQuery(); + + /** + * Return the associated BeanDescriptor. + */ + public BeanDescriptor getBeanDescriptor(); + + /** + * This will create a local (readOnly) transaction if no current transaction + * exists. + *

+ * A transaction may have been passed in explicitly or currently be active + * in the thread local. If not, then a readOnly transaction is created to + * execute this query. + *

+ */ + public void initTransIfRequired(); + + /** + * Will end a locally created transaction. + *

+ * It ends the transaction by using a rollback() as the transaction is known + * to be readOnly. + *

+ */ + public void endTransIfRequired(); + + public void rollbackTransIfRequired(); + + /** + * Execute the query as findById. + */ + public Object findId(); + + /** + * Execute the find row count query. + */ + public int findRowCount(); + + /** + * Execute the find ids query. + */ + public List findIds(); + + /** + * Execute the find returning a QueryIterator and visitor pattern. + */ + public void findVisit(QueryResultVisitor visitor); + + /** + * Execute the find returning a QueryIterator. + */ + public QueryIterator findIterate(); + + /** + * Execute the query as findList. + */ + public List findList(); + + /** + * Execute the query as findSet. + */ + public Set findSet(); + + /** + * Execute the query as findMap. + */ + public Map findMap(); + + /** + * Try to get the object out of the persistence context. + */ + //public T getFromPersistenceContextOrCache(); + + /** + * Try to get the query result from the query cache. + */ + public BeanCollection getFromQueryCache(); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/TraditionalBackgroundExecutor.java b/src/main/java/com/avaje/ebeaninternal/server/core/TraditionalBackgroundExecutor.java new file mode 100644 index 000000000..5edca60b2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/TraditionalBackgroundExecutor.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.concurrent.TimeUnit; + +import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; +import com.avaje.ebeaninternal.server.lib.DaemonScheduleThreadPool; +import com.avaje.ebeaninternal.server.lib.thread.ThreadPool; + +/** + * BackgroundExecutor using my traditional ThreadPool that will grow and trim. + * + * @author rbygrave + */ +public class TraditionalBackgroundExecutor implements SpiBackgroundExecutor { + + private final ThreadPool pool; + + private final DaemonScheduleThreadPool schedulePool; + + /** + * Construct the default implementation of BackgroundExecutor. + */ + public TraditionalBackgroundExecutor(ThreadPool pool, int schedulePoolSize, int shutdownWaitSeconds, String namePrefix) { + this.pool = pool; + this.schedulePool = new DaemonScheduleThreadPool(schedulePoolSize, shutdownWaitSeconds, namePrefix+"-periodic-"); + } + + /** + * Execute a Runnable using a background thread. + */ + public void execute(Runnable r) { + pool.assign(r, true); + } + + public void executePeriodically(Runnable r, long delay, TimeUnit unit) { + schedulePool.scheduleWithFixedDelay(r, delay, delay, unit); + } + + public void shutdown() { + // the pool is shutdown automatically by the ThreadPoolManager + schedulePool.shutdown(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java new file mode 100644 index 000000000..67969c976 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java @@ -0,0 +1,46 @@ +package com.avaje.ebeaninternal.server.core; + +import com.avaje.ebeaninternal.api.SpiTransaction; + +/** + * Used to temporarily wrap a thread local based transaction. + *

+ * Additionally notes if the transaction was created by the request in which + * case it needs to be commited after the request has been processed. + *

+ */ +final class TransWrapper { + + final SpiTransaction transaction; + + private final boolean wasCreated; + + /** + * Wrap the transaction indicating if it was just created. + */ + TransWrapper(SpiTransaction t, boolean created) { + transaction = t; + wasCreated = created; + } + + void commitIfCreated() { + if (wasCreated){ + transaction.commit(); + } + } + + void rollbackIfCreated() { + if (wasCreated){ + transaction.rollback(); + } + } + + /** + * Return true if the transaction was just created. If true it should be + * committed after the request has been processed. + */ + boolean wasCreated() { + return wasCreated; + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/XmlConfig.java b/src/main/java/com/avaje/ebeaninternal/server/core/XmlConfig.java new file mode 100644 index 000000000..f2995c01e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/XmlConfig.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.lib.util.Dnode; + +/** + * Holds the orm.xml and ebean-orm.xml deployment information. + * + * @author rbygrave + */ +public class XmlConfig { + + private final List ebeanOrmXml; + private final List ormXml; + private final List allXml; + + public XmlConfig(List ormXml, List ebeanOrmXml){ + this.ormXml = ormXml; + this.ebeanOrmXml = ebeanOrmXml; + this.allXml = new ArrayList(ormXml.size() + ebeanOrmXml.size()); + allXml.addAll(ormXml); + allXml.addAll(ebeanOrmXml); + } + + public List getEbeanOrmXml() { + return ebeanOrmXml; + } + + public List getOrmXml() { + return ormXml; + } + + public List find(List entityXml, String element) { + ArrayList hits = new ArrayList(); + for (int i = 0; i < entityXml.size(); i++) { + hits.addAll(entityXml.get(i).findAll(element, 1)); + } + return hits; + } + + /** + * Find the deployment xml for a given entity. + *

+ * This searches all the orm.xml and ebean-orm.xml files. + *

+ */ + public List findEntityXml(String className) { + + ArrayList hits = new ArrayList(2); + + for (Dnode ormXml : allXml) { + Dnode entityMappings = ormXml.find("entity-mappings"); + + List entities = entityMappings.findAll("entity", "class", className, 1); + if (entities.size() == 1) { + hits.add(entities.get(0)); + } + } + + return hits; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/XmlConfigLoader.java b/src/main/java/com/avaje/ebeaninternal/server/core/XmlConfigLoader.java new file mode 100644 index 000000000..b8beaec3a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/XmlConfigLoader.java @@ -0,0 +1,192 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.core; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.server.lib.util.Dnode; +import com.avaje.ebeaninternal.server.lib.util.DnodeReader; +import com.avaje.ebeaninternal.server.util.ClassPathReader; +import com.avaje.ebeaninternal.server.util.DefaultClassPathReader; + +/** + * Used to read the orm.xml and ebean-orm.xml configuration files. + * + * @author rbygrave + */ +public class XmlConfigLoader { + + private static final Logger logger = Logger.getLogger(XmlConfigLoader.class.getName()); + + private final ClassPathReader classPathReader; + + private final Object[] classPaths; + + + public XmlConfigLoader(ClassLoader classLoader){ + + if (classLoader == null) { + classLoader = getClass().getClassLoader(); + } + + String cn = GlobalProperties.get("ebean.classpathreader", null); + if (cn != null){ + // use a user defined classPathReader + logger.info("Using ["+cn+"] to read the searchable class path"); + this.classPathReader = (ClassPathReader)ClassUtil.newInstance(cn, this.getClass()); + } else { + this.classPathReader = new DefaultClassPathReader(); + } + + this.classPaths = classPathReader.readPath(classLoader); + } + + public XmlConfig load() { + List ormXml = search("META-INF/orm.xml"); + List ebeanOrmXml = search("META-INF/ebean-orm.xml"); + + return new XmlConfig(ormXml, ebeanOrmXml); + } + + public List search(String searchFor) { + + ArrayList xmlList = new ArrayList(); + + String charsetName = Charset.defaultCharset().name(); + + for (int h = 0; h < classPaths.length; h++) { + + try { + // for each class path ... + File classPath; + if (URL.class.isInstance(classPaths[h])) { + classPath = new File(((URL) classPaths[h]).getFile()); + } else { + classPath = new File(classPaths[h].toString()); + } + + // URL Decode the path replacing %20 to space characters. + String path = URLDecoder.decode(classPath.getAbsolutePath(), charsetName); + + classPath = new File(path); + + if (classPath.isDirectory()) { + checkDir(searchFor, xmlList, classPath); + + } else if (classPath.getName().endsWith(".jar")) { + checkJar(searchFor, xmlList, classPath); + + } else { + // this is not expected + String msg = "Not a Jar or Directory? " + classPath.getAbsolutePath(); + logger.log(Level.SEVERE, msg); + } + + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return xmlList; + + } + + private void processInputStream(ArrayList xmlList, InputStream is) throws IOException { + + DnodeReader reader = new DnodeReader(); + Dnode xmlDoc = reader.parseXml(is); + is.close(); + + xmlList.add(xmlDoc); + } + + private void checkFile(String searchFor, ArrayList xmlList, File dir) throws IOException { + + File f = new File(dir, searchFor); + if (f.exists()){ + FileInputStream fis = new FileInputStream(f); + BufferedInputStream is = new BufferedInputStream(fis); + processInputStream(xmlList, is); + } + } + + private void checkDir(String searchFor, ArrayList xmlList, File dir) throws IOException { + + checkFile(searchFor, xmlList, dir); + + if (dir.getPath().endsWith("classes")) { + // see if this is part of webapp and look for META-INF/searchFor + // relative to the WEB-INF/classes directory + File parent = dir.getParentFile(); + if (parent != null && parent.getPath().endsWith("WEB-INF")){ + parent = parent.getParentFile(); + if (parent != null){ + File metaInf = new File(parent, "META-INF"); + if (metaInf.exists()){ + checkFile(searchFor, xmlList, metaInf); + } + } + } + } + } + + private void checkJar(String searchFor, ArrayList xmlList, File classPath) throws IOException { + + String fileName = classPath.getName(); + if (fileName.toLowerCase().startsWith("surefire")){ + return; + } + JarFile module = null; + try { + module = new JarFile(classPath); + ZipEntry entry = module.getEntry(searchFor); + if (entry != null){ + InputStream is = module.getInputStream(entry); + processInputStream(xmlList, is); + } + } catch (Exception e) { + logger.info("Unable to check jar file "+fileName+" for ebean-orm.xml"); + } finally { + if (module != null){ + module.close(); + } + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/package.html b/src/main/java/com/avaje/ebeaninternal/server/core/package.html new file mode 100644 index 000000000..77c14d3f0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/package.html @@ -0,0 +1,11 @@ + + + + Core implementation objects + + +Core implementation objects + + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractBeanVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractBeanVisitor.java new file mode 100644 index 000000000..f92e37e38 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractBeanVisitor.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.deploy.InheritInfoVisitor; + +/** + * Base BeanVisitor that can help visiting inherited properties. + * + * @author rbygrave + */ +public abstract class AbstractBeanVisitor implements BeanVisitor { + + /** + * Visit all the other inheritance properties that are not on the root. + */ + public void visitInheritanceProperties(BeanDescriptor descriptor, PropertyVisitor pv) { + + InheritInfo inheritInfo = descriptor.getInheritInfo(); + if (inheritInfo != null && inheritInfo.isRoot()){ + // add all properties on the children objects + InheritChildVisitor childVisitor = new InheritChildVisitor(pv); + inheritInfo.visitChildren(childVisitor); + } + } + + + /** + * Helper used to visit all the inheritInfo/BeanDescriptor in + * the inheritance hierarchy (to add their 'local' properties). + */ + protected static class InheritChildVisitor implements InheritInfoVisitor { + + final PropertyVisitor pv; + + protected InheritChildVisitor(PropertyVisitor pv) { + this.pv = pv; + } + + public void visit(InheritInfo inheritInfo) { + BeanProperty[] propertiesLocal = inheritInfo.getBeanDescriptor().propertiesLocal(); + VisitorUtil.visit(propertiesLocal, pv); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractPropertyVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractPropertyVisitor.java new file mode 100644 index 000000000..28c1a0232 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/AbstractPropertyVisitor.java @@ -0,0 +1,41 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; + +/** + * Has no implementation. Can be used as a base object so that + * selective methods can be implemented. + */ +public abstract class AbstractPropertyVisitor implements PropertyVisitor { + + public void visitEmbedded(BeanPropertyAssocOne p) { + } + + public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded) { + } + + public void visitMany(BeanPropertyAssocMany p) { + } + + public void visitOneExported(BeanPropertyAssocOne p) { + } + + public void visitOneImported(BeanPropertyAssocOne p) { + } + + public void visitScalar(BeanProperty p) { + } + + public void visitCompound(BeanPropertyCompound p) { + } + + public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) { + } + + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/AddForeignKeysVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/AddForeignKeysVisitor.java new file mode 100644 index 000000000..e662aba5d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/AddForeignKeysVisitor.java @@ -0,0 +1,149 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.TableJoinColumn; + +/** + * Used to generate the foreign key DDL and related indexes. + */ +public class AddForeignKeysVisitor extends AbstractBeanVisitor { + + final DdlGenContext ctx; + + final FkeyPropertyVisitor pv; + + public AddForeignKeysVisitor(DdlGenContext ctx) { + this.ctx = ctx; + this.pv = new FkeyPropertyVisitor(this, ctx); + } + + public boolean visitBean(BeanDescriptor descriptor) { + if (!descriptor.isInheritanceRoot()){ + // ignore/skip if not a top level BeanDescriptor + return false; + } + return true; + } + + public void visitBeanEnd(BeanDescriptor descriptor) { + + visitInheritanceProperties(descriptor, pv); + } + + public void visitBegin() { + } + + public void visitEnd() { + ctx.addIntersectionFkeys(); + } + + public PropertyVisitor visitProperty(BeanProperty p) { + return pv; + } + + + public static class FkeyPropertyVisitor extends BaseTablePropertyVisitor { + + final DdlGenContext ctx; + + final AddForeignKeysVisitor parent; + + public FkeyPropertyVisitor(AddForeignKeysVisitor parent, DdlGenContext ctx) { + this.parent = parent; + this.ctx = ctx; + } + + @Override + public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded) { + // not interested + } + + @Override + public void visitOneImported(BeanPropertyAssocOne p) { + + // alter table {basetable} add foreign key (...) references {} (...) on delete restrict on update restrict; + // Alter table o_address add Foreign Key (country_code) references o_country (code) on delete restrict on update restrict; + + String baseTable = p.getBeanDescriptor().getBaseTable(); + + TableJoin tableJoin = p.getTableJoin(); + + TableJoinColumn[] columns = tableJoin.columns(); + + + String tableName = p.getBeanDescriptor().getBaseTable(); + String fkName = ctx.getDdlSyntax().getForeignKeyName(tableName, p.getName(), ctx.incrementFkCount()); + + ctx.write("alter table ").write(baseTable).write(" add "); + if (fkName != null) { + ctx.write("constraint ").write(fkName).write(" "); + } + ctx.write("foreign key ("); + for (int i = 0; i < columns.length; i++) { + if (i > 0){ + ctx.write(","); + } + ctx.write(columns[i].getLocalDbColumn()); + } + ctx.write(")"); + + ctx.write(" references "); + ctx.write(tableJoin.getTable()); + ctx.write(" ("); + for (int i = 0; i < columns.length; i++) { + if (i > 0){ + ctx.write(","); + } + ctx.write(columns[i].getForeignDbColumn()); + } + ctx.write(")"); + + String fkeySuffix = ctx.getDdlSyntax().getForeignKeySuffix(); + if (fkeySuffix != null){ + ctx.write(" ").write(fkeySuffix); + } + ctx.write(";").writeNewLine(); + + if (ctx.getDdlSyntax().isRenderIndexForFkey()){ + + //create index idx_fk_o_address_ctry on o_address(country_code); + ctx.write("create index "); + + String idxName = ctx.getDdlSyntax().getIndexName(tableName, p.getName(), ctx.incrementIxCount()); + if (idxName != null){ + ctx.write(idxName); + } + + ctx.write(" on ").write(baseTable).write(" ("); + for (int i = 0; i < columns.length; i++) { + if (i > 0){ + ctx.write(","); + } + ctx.write(columns[i].getLocalDbColumn()); + } + ctx.write(");").writeNewLine(); + } + } + + @Override + public void visitScalar(BeanProperty p) { + // not interested + } + + @Override + public void visitCompound(BeanPropertyCompound p) { + // not interested + } + + @Override + public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) { + // not interested + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/BaseTablePropertyVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/BaseTablePropertyVisitor.java new file mode 100644 index 000000000..b02109deb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/BaseTablePropertyVisitor.java @@ -0,0 +1,60 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; + +/** + * Used to help mark PropertyVisitor methods that need to be implemented + * to visit base table properties. + */ +public abstract class BaseTablePropertyVisitor implements PropertyVisitor { + + /** + * Not required in that you can use the visitEmbeddedScalar. + */ + public void visitEmbedded(BeanPropertyAssocOne p) { + } + + /** + * Override this method. + */ + public abstract void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded); + + /** + * Not part of base table. + */ + public void visitMany(BeanPropertyAssocMany p) { + } + + /** + * Not part of base table. + */ + public void visitOneExported(BeanPropertyAssocOne p) { + } + + /** + * Override this method for the foreign key. + */ + public abstract void visitOneImported(BeanPropertyAssocOne p); + + /** + * Override this method for normal scalar property. + */ + public abstract void visitScalar(BeanProperty p); + + /** + * Not required in that the scalar properties map to the columns. + */ + public void visitCompound(BeanPropertyCompound p){ + } + + /** + * Override this method for scalar property inside a Immutable Compound Value object. + */ + public abstract void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p); + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/BeanVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/BeanVisitor.java new file mode 100644 index 000000000..f5f4b0ee5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/BeanVisitor.java @@ -0,0 +1,42 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Visitor pattern for visiting a BeanDescriptor and potentially all its bean + * properties. + */ +public interface BeanVisitor { + + /** + * Starting. + */ + public void visitBegin(); + + /** + * Visit a BeanDescriptor and return true to continue visiting the bean + * (return false to skip visiting this bean). + */ + public boolean visitBean(BeanDescriptor descriptor); + + /** + * Visit a property potentially return a specific PropertyVisitor. + *

+ * A PropertyVisitor can be returned to more easily process bean properties + * by their specific type. + *

+ */ + public PropertyVisitor visitProperty(BeanProperty p); + + /** + * Finished visiting the BeanDescriptor. + */ + public void visitBeanEnd(BeanDescriptor descriptor); + + /** + * Finished all visiting. + */ + public void visitEnd(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateIntersectionTable.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateIntersectionTable.java new file mode 100644 index 000000000..c3766270a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateIntersectionTable.java @@ -0,0 +1,173 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.TableJoinColumn; + + +/** + * Create the sql to create the intersection table and its foreign key + * constraints. + */ +public class CreateIntersectionTable { + + private static final String NEW_LINE = "\n"; + + private final DdlGenContext ctx; + + private final BeanPropertyAssocMany manyProp; + private final TableJoin intersectionTableJoin; + private final TableJoin tableJoin; + + private StringBuilder sb = new StringBuilder(); + + private StringBuilder pkeySb = new StringBuilder(); + + private int foreignKeyCount; + + private int maxFkeyLength; + + public CreateIntersectionTable(DdlGenContext ctx, BeanPropertyAssocMany manyProp) { + this.ctx = ctx; + this.manyProp = manyProp; + this.intersectionTableJoin = manyProp.getIntersectionTableJoin(); + this.tableJoin = manyProp.getTableJoin(); + this.maxFkeyLength = ctx.getDdlSyntax().getMaxConstraintNameLength()-3; + } + + public void build() { + + String createTable = buildCreateTable(); + ctx.addCreateIntersectionTable(createTable); + + foreignKeyCount = 0; + buildFkConstraints(); + } + + private void buildFkConstraints() { + + BeanDescriptor localDesc = manyProp.getBeanDescriptor(); + String fk1 = buildFkConstraints(localDesc, intersectionTableJoin.columns(), true); + ctx.addIntersectionTableFk(fk1); + + BeanDescriptor targetDesc = manyProp.getTargetDescriptor(); + String fk2 = buildFkConstraints(targetDesc, tableJoin.columns(), false); + ctx.addIntersectionTableFk(fk2); + + } + + + + private String getFkNameSuffix() { + + foreignKeyCount++; + + if (foreignKeyCount > 9){ + return "_"+foreignKeyCount; + } else { + return "_0"+foreignKeyCount; + } + } + + private String getFkNameWithSuffix(String fkName) { + if (fkName.length() > maxFkeyLength){ + fkName = fkName.substring(0, maxFkeyLength); + } + return fkName+getFkNameSuffix(); + } + + private String buildFkConstraints(BeanDescriptor desc, TableJoinColumn[] columns, boolean direction) { + + + StringBuilder fkBuf = new StringBuilder(); + + String fkName = "fk_"+intersectionTableJoin.getTable()+"_"+desc.getBaseTable(); + + fkName = getFkNameWithSuffix(fkName); + + fkBuf.append("alter table "); + fkBuf.append(intersectionTableJoin.getTable()); + fkBuf.append(" add constraint ").append(fkName); + + fkBuf.append(" foreign key ("); + + for (int i = 0; i < columns.length; i++) { + if (i > 0) { + fkBuf.append(", "); + } + String col = direction ? columns[i].getForeignDbColumn() : columns[i].getLocalDbColumn(); + fkBuf.append(col); + } + fkBuf.append(") references ").append(desc.getBaseTable()).append(" ("); + + for (int i = 0; i < columns.length; i++) { + if (i > 0) { + fkBuf.append(", "); + } + String col = !direction ? columns[i].getForeignDbColumn() : columns[i].getLocalDbColumn(); + fkBuf.append(col); + } + fkBuf.append(")"); + + String fkeySuffix = ctx.getDdlSyntax().getForeignKeySuffix(); + if (fkeySuffix != null){ + fkBuf.append(" "); + fkBuf.append(fkeySuffix); + } + fkBuf.append(";").append(NEW_LINE); + + return fkBuf.toString(); + } + + private String buildCreateTable() { + + BeanDescriptor localDesc = manyProp.getBeanDescriptor(); + BeanDescriptor targetDesc = manyProp.getTargetDescriptor(); + + sb.append("create table "); + sb.append(intersectionTableJoin.getTable()); + sb.append(" (").append(NEW_LINE); + + TableJoinColumn[] columns = intersectionTableJoin.columns(); + for (int i = 0; i < columns.length; i++) { + + addColumn(localDesc, columns[i].getForeignDbColumn(), columns[i].getLocalDbColumn()); + } + + TableJoinColumn[] otherColumns = tableJoin.columns(); + for (int i = 0; i < otherColumns.length; i++) { + + addColumn(targetDesc, otherColumns[i].getLocalDbColumn(), otherColumns[i].getForeignDbColumn()); + } + + sb.append(" constraint pk_").append(intersectionTableJoin.getTable()); + sb.append(" primary key (").append(pkeySb.toString().substring(2)); + sb.append("))").append(NEW_LINE).append(";").append(NEW_LINE); + + return sb.toString(); + } + + private void addColumn(BeanDescriptor desc, String column, String findPropColumn) { + + pkeySb.append(", "); + pkeySb.append(column); + + writeColumn(column); + + BeanProperty p = desc.getIdBinder().findBeanProperty(findPropColumn); + if (p == null) { + throw new RuntimeException("Could not find id property for " + findPropColumn); + } + + String columnDefn = ctx.getColumnDefn(p); + sb.append(columnDefn); + sb.append(" not null"); + sb.append(",").append(NEW_LINE); + } + + private void writeColumn(String columnName) { + sb.append(" ").append(ctx.pad(columnName, 30)).append(" "); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateSequenceVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateSequenceVisitor.java new file mode 100644 index 000000000..e9352f60f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateSequenceVisitor.java @@ -0,0 +1,64 @@ +package com.avaje.ebeaninternal.server.ddl; + +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to generate the drop table DDL script. + */ +public class CreateSequenceVisitor implements BeanVisitor { + + private static final Logger logger = Logger.getLogger(DropSequenceVisitor.class.getName()); + + private final DdlGenContext ctx; + + private final boolean supportsSequence; + + public CreateSequenceVisitor(DdlGenContext ctx) { + this.ctx = ctx; + this.supportsSequence = ctx.getDbPlatform().getDbIdentity().isSupportsSequence(); + } + + public boolean visitBean(BeanDescriptor descriptor) { + + if (!descriptor.isInheritanceRoot()){ + return false; + } + if (descriptor.getSequenceName() == null) { + return false; + } + + if (!supportsSequence){ + // Hopefully a generic test case + String msg = "Not creating sequence "+descriptor.getSequenceName()+" on Bean "+descriptor.getName() + +" as DatabasePlatform does not support sequences"; + logger.warning(msg); + return false; + } + + if (descriptor.getSequenceName() != null) { + ctx.write("create sequence "); + ctx.write(descriptor.getSequenceName()); + ctx.write(";").writeNewLine().writeNewLine(); + } + return true; + } + + + public void visitBeanEnd(BeanDescriptor descriptor) { + } + + public void visitBegin() { + } + + public void visitEnd() { + } + + public PropertyVisitor visitProperty(BeanProperty p) { + // Return null as we are not interested in properties + return null; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableColumnVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableColumnVisitor.java new file mode 100644 index 000000000..64d485acb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableColumnVisitor.java @@ -0,0 +1,223 @@ +package com.avaje.ebeaninternal.server.ddl; + +import java.sql.Types; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.config.dbplatform.DbDdlSyntax; +import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.TableJoinColumn; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +/** + * Used as part of CreateTableVisitor to generated the create table DDL script. + */ +public class CreateTableColumnVisitor extends BaseTablePropertyVisitor { + + private static final Logger logger = Logger.getLogger(CreateTableColumnVisitor.class.getName()); + + private final DdlGenContext ctx; + + private final DbDdlSyntax ddl; + + private final CreateTableVisitor parent; + + public CreateTableColumnVisitor(CreateTableVisitor parent, DdlGenContext ctx) { + this.parent = parent; + this.ctx = ctx; + this.ddl = ctx.getDdlSyntax(); + } + + + @Override + public void visitMany(BeanPropertyAssocMany p) { + if (p.isManyToMany()){ + if (p.getMappedBy() != null) { + // only create on other 'owning' side + + } else { + TableJoin intersectionTableJoin = p.getIntersectionTableJoin(); + + // check if the intersection table has already been created + String intTable = intersectionTableJoin.getTable(); + if (ctx.isProcessIntersectionTable(intTable)){ + // build the create table and fkey constraints + // putting the DDL into ctx for later output as we are + // in the middle of rendering the create table DDL + new CreateIntersectionTable(ctx, p).build(); + } + } + } + } + + + public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) { + visitScalar(p); + } + + + public void visitCompound(BeanPropertyCompound p) { + // do nothing + } + + + @Override + public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded) { + + visitScalar(p); + } + + private StringBuilder createUniqueConstraintBuffer(String table, String column) { + + String uqConstraintName = "uq_"+ table+ "_"+column; + + if (uqConstraintName.length() > ddl.getMaxConstraintNameLength()){ + uqConstraintName = uqConstraintName.substring(0, ddl.getMaxConstraintNameLength()); + } + + uqConstraintName = ctx.removeQuotes(uqConstraintName); + uqConstraintName = StringHelper.replaceString(uqConstraintName, " ", "_"); + + StringBuilder constraintExpr = new StringBuilder(); + constraintExpr.append("constraint ") + .append(uqConstraintName) + .append(" unique ("); + + return constraintExpr; + } + + @Override + public void visitOneImported(BeanPropertyAssocOne p) { + + ImportedId importedId = p.getImportedId(); + + TableJoinColumn[] columns = p.getTableJoin().columns(); + if (columns.length == 0){ + String msg = "No join columns for "+p.getFullBeanName(); + throw new RuntimeException(msg); + } + + StringBuilder constraintExpr = createUniqueConstraintBuffer(p.getBeanDescriptor().getBaseTable(), columns[0].getLocalDbColumn()); + + for (int i = 0; i < columns.length; i++) { + + String dbCol = columns[i].getLocalDbColumn(); + + if (i > 0){ + constraintExpr.append(", "); + } + constraintExpr.append(dbCol); + + if (parent.isDbColumnWritten(dbCol)) { + continue; + } + + parent.writeColumnName(dbCol, p); + + BeanProperty importedProperty = importedId.findMatchImport(dbCol); + if (importedProperty != null) { + + String columnDefn = ctx.getColumnDefn(importedProperty); + ctx.write(columnDefn); + + } else { + throw new RuntimeException("Imported BeanProperty not found?"); + } + + if (!p.isNullable()) { + ctx.write(" not null"); + } + ctx.write(",").writeNewLine(); + } + constraintExpr.append(")"); + + if (p.isOneToOne()){ + if (ddl.isAddOneToOneUniqueContraint()){ + parent.addUniqueConstraint( constraintExpr.toString()); + } + } + + } + + @Override + public void visitScalar(BeanProperty p) { + + if (p.isSecondaryTable()) { + return; + } + + if (parent.isDbColumnWritten(p.getDbColumn())) { + return; + } + + parent.writeColumnName(p.getDbColumn(), p); + + String columnDefn = ctx.getColumnDefn(p); + ctx.write(columnDefn); + + if (isIdentity(p)) { + writeIdentity(); + } + + if (p.isId() && ddl.isInlinePrimaryKeyConstraint()){ + ctx.write(" primary key"); + + } else if (!p.isNullable() || p.isDDLNotNull()) { + ctx.write(" not null"); + } + + if (p.isUnique() && !p.isId()){ + parent.addUniqueConstraint(createUniqueConstraint(p)); + } + + parent.addCheckConstraint(p); + + ctx.write(",").writeNewLine(); + } + + private String createUniqueConstraint(BeanProperty p) { + + StringBuilder expr = createUniqueConstraintBuffer(p.getBeanDescriptor().getBaseTable(), p.getDbColumn()); + + expr.append(p.getDbColumn()).append(")"); + + return expr.toString(); + } + + protected void writeIdentity() { + String identity = ddl.getIdentity(); + if (identity != null && identity.length() > 0) { + ctx.write(" ").write(identity); + } + } + + protected boolean isIdentity(BeanProperty p) { + + if (p.isId()) { + try { + IdType idType = p.getBeanDescriptor().getIdType(); + + if (idType.equals(IdType.IDENTITY)){ + + int jdbcType = p.getScalarType().getJdbcType(); + if (jdbcType == Types.INTEGER || jdbcType == Types.BIGINT || jdbcType == Types.SMALLINT) { + + return true; + } + } + } catch (Exception e){ + String msg = "Error determining identity on property "+p.getFullBeanName(); + logger.log(Level.SEVERE, msg, e); + } + } + return false; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableVisitor.java new file mode 100644 index 000000000..5641e5209 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateTableVisitor.java @@ -0,0 +1,286 @@ +package com.avaje.ebeaninternal.server.ddl; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import com.avaje.ebean.config.dbplatform.DbDdlSyntax; +import com.avaje.ebean.config.dbplatform.DbType; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.deploy.parse.SqlReservedWords; + +/** + * Used to generated the create table DDL script. + */ +public class CreateTableVisitor extends AbstractBeanVisitor { + + protected static final Logger logger = Logger.getLogger(CreateTableVisitor.class.getName()); + + final DdlGenContext ctx; + + final PropertyVisitor pv; + + final DbDdlSyntax ddl; + + final int columnNameWidth; + + // avoid writing columns twice, e.g. when used in associations with insertable=false and updateable=false + private final Set wroteColumns = new HashSet(); + + private ArrayList checkConstraints = new ArrayList(); + + private ArrayList uniqueConstraints = new ArrayList(); + + private String table; + private String schema; + + public Set getWroteColumns() { + return wroteColumns; + } + + + public CreateTableVisitor(DdlGenContext ctx) { + this.ctx = ctx; + this.ddl = ctx.getDdlSyntax(); + this.columnNameWidth = ddl.getColumnNameWidth(); + this.pv = new CreateTableColumnVisitor(this, ctx); + } + + public boolean isDbColumnWritten(String dbColumn) { + // Columns are not case sensitive - user lower case + // as e.g. @JoinColumn(s) may use a different case + return wroteColumns.contains(dbColumn.toLowerCase()); + } + + public void addDbColumnWritten(String dbColumn){ + // Column names are case insensitive + wroteColumns.add(dbColumn.toLowerCase()); + } + + /** + * Write the table name including a check for SQL reserved words. + */ + protected void writeTableName(BeanDescriptor descriptor) { + + String tableName = descriptor.getBaseTable(); + int dotPos = tableName.lastIndexOf('.'); + if (dotPos > -1){ + schema = tableName.substring(0, dotPos); + table = tableName.substring(dotPos+1); + } else { + table = tableName; + } + + if (SqlReservedWords.isKeyword(table)) { + logger.warning("Table name ["+table+"] is a suspected SQL reserved word for bean "+descriptor.getFullName()); + } + + ctx.write(tableName); + } + + protected String getTable() { + return table; + } + + protected String getSchema() { + return schema; + } + + /** + * Write the column name including a check for the SQL reserved words. + * @param p + */ + protected void writeColumnName(String columnName, BeanProperty p) { + + addDbColumnWritten(columnName); + + if (SqlReservedWords.isKeyword(columnName)) { + String propName = p == null ? "(Unknown)" : p.getFullBeanName(); + logger.warning("Column name ["+columnName+"] is a suspected SQL reserved word for property "+propName); + } + + ctx.write(" ").write(columnName, columnNameWidth).write(" "); + } + + /** + * Build a check constraint for the property if required. + *

+ * Typically check constraint based on Enum mapping values. + *

+ */ + protected void addCheckConstraint(BeanProperty p, String prefix, String constraintExpression) { + + if (p != null && constraintExpression != null){ + + // build constraint clause + String s = "constraint "+getConstraintName(prefix, p)+" "+constraintExpression; + + // add to list as we render all check constraints just prior to primary key + checkConstraints.add(s); + } + } + + protected String getConstraintName(String prefix, BeanProperty p) { + return prefix + table + "_" + p.getDbColumn(); + } + + protected void addUniqueConstraint(String constraintExpression) { + uniqueConstraints.add(constraintExpression); + } + + protected void addCheckConstraint(String constraintExpression) { + checkConstraints.add(constraintExpression); + } + + protected void addCheckConstraint(BeanProperty p) { + addCheckConstraint(p,"ck_", p.getDbConstraintExpression()); + } + + public boolean visitBean(BeanDescriptor descriptor) { + + wroteColumns.clear(); + + if (!descriptor.isInheritanceRoot()){ + return false; + } + + ctx.write("create table "); + writeTableName(descriptor); + ctx.write(" (").writeNewLine(); + + InheritInfo inheritInfo = descriptor.getInheritInfo(); + if (inheritInfo != null && inheritInfo.isRoot()){ + String discColumn = inheritInfo.getDiscriminatorColumn(); + int discType = inheritInfo.getDiscriminatorType(); + int discLength = inheritInfo.getDiscriminatorLength(); + DbType dbType = ctx.getDbTypeMap().get(discType); + String discDbType = dbType.renderType(discLength, 0); + + writeColumnName(discColumn, null); + ctx.write(discDbType); + ctx.write(" not null,"); + ctx.writeNewLine(); + + } + + return true; + } + + public void visitBeanEnd(BeanDescriptor descriptor) { + + visitInheritanceProperties(descriptor, pv); + + if (checkConstraints.size() > 0){ + for (String checkConstraint : checkConstraints) { + ctx.write(" ").write(checkConstraint).write(",").writeNewLine(); + } + checkConstraints = new ArrayList(); + } + + if (uniqueConstraints.size() > 0){ + for (String constraint : uniqueConstraints) { + ctx.write(" ").write(constraint).write(",").writeNewLine(); + } + uniqueConstraints = new ArrayList(); + } + + CompoundUniqueContraint[] compoundUniqueConstraints = descriptor.getCompoundUniqueConstraints(); + if (compoundUniqueConstraints != null){ + String table = descriptor.getBaseTable(); + for (int i = 0; i < compoundUniqueConstraints.length; i++) { + String constraint = createUniqueConstraint(table, i, compoundUniqueConstraints[i]); + ctx.write(" ").write(constraint).write(",").writeNewLine(); + } + } + + + BeanProperty[] ids = descriptor.propertiesId(); + + if (ids.length == 0){ + // No comma + new line + ctx.removeLast().removeLast(); + } else if (ids.length > 1 || ddl.isInlinePrimaryKeyConstraint()) { + // The Primary Key constraint was inlined with the column + // ... No comma + new line + ctx.removeLast().removeLast(); + + } else { + // Add the primay key constraint + String pkName = ddl.getPrimaryKeyName(table); + ctx.write(" constraint ").write(pkName).write(" primary key ("); + + VisitorUtil.visit(ids, new AbstractPropertyVisitor() { + + @Override + public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded) { + ctx.write(p.getDbColumn()).write(", "); + } + + @Override + public void visitScalar(BeanProperty p) { + ctx.write(p.getDbColumn()).write(", "); + } + + @Override + public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) { + ctx.write(p.getDbColumn()).write(", "); + } + + }); + // remove the last comma, end of PK + ctx.removeLast().write(")"); + } + + // end of table + ctx.write(")").writeNewLine(); + ctx.write(";").writeNewLine().writeNewLine(); + ctx.flush(); + } + + private String createUniqueConstraint(String table, int idx, CompoundUniqueContraint uc) { + + String uqConstraintName = "uq_"+table+"_"+(idx+1) ; + + StringBuilder sb = new StringBuilder(); + sb.append("constraint ") + .append(uqConstraintName) + .append(" unique ("); + + String[] columns = uc.getColumns(); + + for (int i = 0; i < columns.length; i++) { + if (i > 0){ + sb.append(","); + } + sb.append(columns[i]); + } + sb.append(")"); + + return sb.toString(); + } + + public void visitBeanDescriptorEnd() { + ctx.write(");").writeNewLine().writeNewLine(); + } + + + public PropertyVisitor visitProperty(BeanProperty p) { + return pv; + } + + public void visitBegin() { + + } + + public void visitEnd() { + ctx.addIntersectionCreateTables(); + ctx.flush(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenContext.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenContext.java new file mode 100644 index 000000000..ecf28ea46 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenContext.java @@ -0,0 +1,259 @@ +/** + * Imilia Interactive Mobile Applications GmbH + * Copyright (c) 2009 - all rights reserved + * + * Created on: Jun 29, 2009 + * Created by: emcgreal + */ +package com.avaje.ebeaninternal.server.ddl; + +import java.io.StringWriter; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.DbType; +import com.avaje.ebean.config.dbplatform.DbTypeMap; +import com.avaje.ebean.config.dbplatform.DbDdlSyntax; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.type.ScalarType; + +/** + * The context used during DDL generation. + */ +public class DdlGenContext { + + private final StringWriter stringWriter = new StringWriter(); + + /** + * Used to map bean types to DB specific types. + */ + private final DbTypeMap dbTypeMap; + + /** + * Handles DB specific DDL syntax. + */ + private final DbDdlSyntax ddlSyntax; + + /** + * The new line character that is used. + */ + private final String newLine; + + /** + * Last content written (used with removeLast()) + */ + private final List contentBuffer = new ArrayList(); + + private Set intersectionTables = new HashSet(); + + private List intersectionTablesCreateDdl = new ArrayList(); + private List intersectionTablesFkDdl = new ArrayList(); + + private final DatabasePlatform dbPlatform; + + /** The Naming convention used to define FK an IX names */ + private final NamingConvention namingConvention; + + /** The global fk count used to keep FK names unique */ + private int fkCount; + + /** The ix count. */ + private int ixCount; + + public DdlGenContext(DatabasePlatform dbPlatform, NamingConvention namingConvention){ + this.dbPlatform = dbPlatform; + this.dbTypeMap = dbPlatform.getDbTypeMap(); + this.ddlSyntax = dbPlatform.getDbDdlSyntax(); + this.newLine = ddlSyntax.getNewLine(); + this.namingConvention = namingConvention; + } + + /** + * Return the dbPlatform. + */ + public DatabasePlatform getDbPlatform() { + return dbPlatform; + } + + public boolean isProcessIntersectionTable(String tableName){ + return intersectionTables.add(tableName); + } + + public void addCreateIntersectionTable(String createTableDdl){ + intersectionTablesCreateDdl.add(createTableDdl); + } + + public void addIntersectionTableFk(String intTableFk){ + intersectionTablesFkDdl.add(intTableFk); + } + + public void addIntersectionCreateTables() { + for (String intTableCreate : intersectionTablesCreateDdl) { + write(newLine); + write(intTableCreate); + } + } + + public void addIntersectionFkeys() { + write(newLine); + write(newLine); + for (String intTableFk : intersectionTablesFkDdl) { + write(newLine); + write(intTableFk); + } + } + + /** + * Return the generated content (DDL script). + */ + public String getContent(){ + return stringWriter.toString(); + } + + /** + * Return the map used to determine the DB specific type + * for a given bean property. + */ + public DbTypeMap getDbTypeMap() { + return dbTypeMap; + } + + /** + * Return object to handle DB specific DDL syntax. + */ + public DbDdlSyntax getDdlSyntax() { + return ddlSyntax; + } + + public String getColumnDefn(BeanProperty p) { + DbType dbType = getDbType(p); + return p.renderDbType(dbType); + } + + private DbType getDbType(BeanProperty p) { + + ScalarType scalarType = p.getScalarType(); + if (scalarType == null) { + throw new RuntimeException("No scalarType for " + p.getFullBeanName()); + } + + if (p.isDbEncrypted()){ + return dbTypeMap.get(p.getDbEncryptedType()); + } + + int jdbcType = scalarType.getJdbcType(); + if (p.isLob() && jdbcType == Types.VARCHAR){ + // workaround for Postgres TEXT type which is + // VARCHAR in jdbc API but TEXT in ddl + jdbcType = Types.CLOB; + } + return dbTypeMap.get(jdbcType); + } + /** + * Write content to the buffer. + */ + public DdlGenContext write(String content, int minWidth){ + + content = pad(content, minWidth); + + contentBuffer.add(content); + + return this; + + } + + /** + * Write content to the buffer. + */ + public DdlGenContext write(String content){ + return write(content, 0); + } + + public DdlGenContext writeNewLine() { + write(newLine); + return this; + } + + /** + * Remove the last content that was written. + */ + public DdlGenContext removeLast() { + if (!contentBuffer.isEmpty()){ + contentBuffer.remove(contentBuffer.size()-1); + } else { + throw new RuntimeException("No lastContent to remove?"); + } + return this; + } + + /** + * Flush the content to the buffer. + */ + public DdlGenContext flush() { + if (!contentBuffer.isEmpty()){ + for (String s:contentBuffer){ + + if (s != null){ + stringWriter.write(s); + } + } + contentBuffer.clear(); + } + return this; + } + + private String padding(int length){ + + StringBuffer sb = new StringBuffer(length); + for (int i = 0; i < length; i++) { + sb.append(" "); + } + return sb.toString(); + } + + public String pad(String content, int minWidth){ + if (minWidth > 0 && content.length() < minWidth){ + int padding = minWidth - content.length(); + return content + padding(padding); + } + return content; + } + + /** + * @return the namingConvention + */ + public NamingConvention getNamingConvention() { + return namingConvention; + } + + /** + * @return the incremented fkCount + */ + public int incrementFkCount() { + return ++fkCount; + } + + /** + * @return the incremented ixCount + */ + public int incrementIxCount() { + return ++ixCount; + } + + /** + * Strips off the Database Platform specific quoted identifier characters. + */ + public String removeQuotes(String dbColumn) { + + dbColumn = StringHelper.replaceString(dbColumn, dbPlatform.getOpenQuote(), ""); + dbColumn = StringHelper.replaceString(dbColumn, dbPlatform.getCloseQuote(), ""); + + return dbColumn; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java new file mode 100644 index 000000000..bc6a18877 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java @@ -0,0 +1,342 @@ +package com.avaje.ebeaninternal.server.ddl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintStream; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.Transaction; +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.api.SpiEbeanServer; + +/** + * Controls the generation of DDL and potentially runs the resulting scripts. + */ +public class DdlGenerator { + + private static final Logger logger = Logger.getLogger(DdlGenerator.class.getName()); + + private final SpiEbeanServer server; + + private final DatabasePlatform dbPlatform; + + private PrintStream out = System.out; + + private int summaryLength = 80; + + private boolean debug = true; + + private boolean generateDdl; + private boolean runDdl; + + private String dropContent; + private String createContent; + + private NamingConvention namingConvention; + + public DdlGenerator(SpiEbeanServer server, DatabasePlatform dbPlatform, ServerConfig serverConfig) { + this.server = server; + this.dbPlatform = dbPlatform; + this.generateDdl = serverConfig.isDdlGenerate(); + this.runDdl = serverConfig.isDdlRun(); + this.namingConvention = serverConfig.getNamingConvention(); + } + + /** + * Generate the DDL and then run the DDL based on property settings (ebean.ddl.generate and ebean.ddl.run etc). + */ + public void execute(boolean online) { + generateDdl(); + if (online){ + runDdl(); + } + } + + /** + * Generate the DDL drop and create scripts if the properties have been set. + */ + public void generateDdl() { + if (generateDdl) { + writeDrop(getDropFileName()); + writeCreate(getCreateFileName()); + } + } + + /** + * Run the DDL drop and DDL create scripts if properties have been set. + */ + public void runDdl() { + + if (runDdl) { + try { + if (dropContent == null){ + dropContent = readFile(getDropFileName()); + } + if (createContent == null){ + createContent = readFile(getCreateFileName()); + } + runScript(true, dropContent); + runScript(false, createContent); + + } catch (IOException e){ + String msg = "Error reading drop/create script from file system"; + throw new RuntimeException(msg, e); + } + } + } + + protected void writeDrop(String dropFile) { + +// String c = generateDropDdl(); + try { + String c = generateDropDdl(); + writeFile(dropFile, c); + + } catch (IOException e) { + String msg = "Error generating Drop DDL"; + throw new PersistenceException(msg, e); + } + } + + protected void writeCreate(String createFile) { + +// String c = generateCreateDdl(); + try { + String c = generateCreateDdl(); + writeFile(createFile, c); + + } catch (IOException e) { + String msg = "Error generating Create DDL"; + throw new PersistenceException(msg, e); + } + } + + public String generateDropDdl() { + + DdlGenContext ctx = createContext(); + + DropTableVisitor drop = new DropTableVisitor(ctx); + VisitorUtil.visit(server, drop); + + DropSequenceVisitor dropSequence = new DropSequenceVisitor(ctx); + VisitorUtil.visit(server, dropSequence); + + ctx.flush(); + dropContent = ctx.getContent(); + return dropContent; + } + + public String generateCreateDdl() { + + DdlGenContext ctx = createContext(); + CreateTableVisitor create = new CreateTableVisitor(ctx); + VisitorUtil.visit(server, create); + + CreateSequenceVisitor createSequence = new CreateSequenceVisitor(ctx); + VisitorUtil.visit(server, createSequence); + + AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(ctx); + VisitorUtil.visit(server, fkeys); + + ctx.flush(); + createContent = ctx.getContent(); + return createContent; + } + + protected String getDropFileName() { + return server.getName() + "-drop.sql"; + } + + protected String getCreateFileName() { + return server.getName() + "-create.sql"; + } + + protected DdlGenContext createContext() { + return new DdlGenContext(dbPlatform, namingConvention); + } + + protected void writeFile(String fileName, String fileContent) throws IOException { + + File f = new File(fileName); + + FileWriter fw = new FileWriter(f); + try { + fw.write(fileContent); + fw.flush(); + } finally { + fw.close(); + } + } + + protected String readFile(String fileName) throws IOException { + + File f = new File(fileName); + if (!f.exists()){ + return null; + } + + StringBuilder buf = new StringBuilder(); + + FileReader fr = new FileReader(f); + LineNumberReader lr = new LineNumberReader(fr); + try { + String s = null; + while ((s = lr.readLine()) != null){ + buf.append(s).append("\n"); + } + } finally { + lr.close(); + } + + return buf.toString(); + } + + /** + * Execute all the DDL statements in the script. + */ + public void runScript(boolean expectErrors, String content) { + + StringReader sr = new StringReader(content); + List statements = parseStatements(sr); + + Transaction t = server.createTransaction(); + try { + Connection connection = t.getConnection(); + + out.println("runScript"); + out.flush(); + + runStatements(expectErrors, statements, connection); + + out.println("... end of script"); + out.flush(); + + t.commit(); + + } catch (Exception e){ + String msg = "Error: "+e.getMessage(); + throw new PersistenceException(msg, e); + } finally { + t.end(); + } + } + + /** + * Execute the list of statements. + */ + private void runStatements(boolean expectErrors, List statements, Connection c) { + + for (int i = 0; i < statements.size(); i++) { + String xOfy = (i + 1) + " of " + statements.size(); + runStatement(expectErrors, xOfy, statements.get(i), c); + } + } + + /** + * Execute the statement. + */ + private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) { + + PreparedStatement pstmt = null; + try { + + // trim and remove trailing ; or / + stmt = stmt.trim(); + if (stmt.endsWith(";")){ + stmt = stmt.substring(0, stmt.length()-1); + } else if (stmt.endsWith("/")) { + stmt = stmt.substring(0, stmt.length()-1); + } + + if (debug) { + out.println("executing "+oneOf+" "+ getSummary(stmt)); + out.flush(); + } + + pstmt = c.prepareStatement(stmt); + pstmt.execute(); + + } catch (Exception e) { + if (expectErrors){ + out.println(" ... ignoring error executing "+getSummary(stmt)+" error: "+e.getMessage()); + e.printStackTrace(); + out.flush(); + } else { + String msg = "Error executing stmt[" + stmt+"] error["+e.getMessage()+"]"; + throw new RuntimeException(msg, e); + } + } finally { + if (pstmt != null){ + try { + pstmt.close(); + } catch (SQLException e){ + logger.log(Level.SEVERE, "Error closing pstmt", e); + } + } + } + } + + /** + * Break up the sql in reader into a list of statements using the semi-colon + * character; + */ + protected List parseStatements(StringReader reader) { + + try { + BufferedReader br = new BufferedReader(reader); + + ArrayList statements = new ArrayList(); + + StringBuilder sb = new StringBuilder(); + String s; + while ((s = br.readLine()) != null) { + s = s.trim(); + int semiPos = s.indexOf(';'); + if (semiPos == -1) { + sb.append(s).append(" "); + + } else if (semiPos == s.length()-1) { + // semicolon at end of line + sb.append(s); + statements.add(sb.toString().trim()); + sb = new StringBuilder(); + + } else { + // semicolon in middle of line + String preSemi = s.substring(0, semiPos); + sb.append(preSemi); + statements.add(sb.toString().trim()); + sb = new StringBuilder(); + sb.append(s.substring(semiPos+1)); + + } + } + + return statements; + } catch (IOException e) { + throw new PersistenceException(e); + } + } + + private String getSummary(String s){ + if (s.length() > summaryLength){ + return s.substring(0, summaryLength).trim()+"..."; + } + return s; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/DropSequenceVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/DropSequenceVisitor.java new file mode 100644 index 000000000..27332add5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/DropSequenceVisitor.java @@ -0,0 +1,69 @@ +package com.avaje.ebeaninternal.server.ddl; + +import java.util.logging.Logger; + +import com.avaje.ebean.config.dbplatform.DbDdlSyntax; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to generate the drop table DDL script. + */ +public class DropSequenceVisitor implements BeanVisitor { + + private static final Logger logger = Logger.getLogger(DropSequenceVisitor.class.getName()); + + private final DdlGenContext ctx; + + private final DbDdlSyntax ddlSyntax; + + private final boolean supportsSequence; + + public DropSequenceVisitor(DdlGenContext ctx) { + this.ctx = ctx; + this.ddlSyntax = ctx.getDdlSyntax(); + this.supportsSequence = ctx.getDbPlatform().getDbIdentity().isSupportsSequence(); + } + + public boolean visitBean(BeanDescriptor descriptor) { + + + if (!descriptor.isInheritanceRoot()){ + return false; + } + if (descriptor.getSequenceName() != null) { + + if (!supportsSequence){ + // Hopefully a generic test case + String msg = "Not dropping sequence "+descriptor.getSequenceName()+" on Bean "+descriptor.getName() + +" as DatabasePlatform does not support sequences"; + logger.finer(msg); + return false; + } + + ctx.write("drop sequence "); + if (ddlSyntax.getDropIfExists() != null){ + ctx.write(ddlSyntax.getDropIfExists()).write(" "); + } + ctx.write(descriptor.getSequenceName()); + ctx.write(";").writeNewLine().writeNewLine(); + } + return true; + } + + + public void visitBeanEnd(BeanDescriptor descriptor) { + } + + public void visitBegin() { + } + + public void visitEnd() { + } + + public PropertyVisitor visitProperty(BeanProperty p) { + // Return null as we are not interested in properties + return null; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/DropTableVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/DropTableVisitor.java new file mode 100644 index 000000000..7978f39ca --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/DropTableVisitor.java @@ -0,0 +1,88 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebean.config.dbplatform.DbDdlSyntax; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; + +/** + * Used to generate the drop table DDL script. + */ +public class DropTableVisitor implements BeanVisitor { + + final DdlGenContext ctx; + + final DbDdlSyntax ddlSyntax; + + public DropTableVisitor(DdlGenContext ctx) { + this.ctx = ctx; + this.ddlSyntax = ctx.getDdlSyntax(); + } + + protected void writeDropTable(BeanDescriptor descriptor){ + writeDropTable(descriptor.getBaseTable()); + } + + protected void writeDropTable(String tableName){ + ctx.write("drop table "); + if (ddlSyntax.getDropIfExists() != null){ + ctx.write(ddlSyntax.getDropIfExists()).write(" "); + } + ctx.write(tableName); + + if (ddlSyntax.getDropTableCascade() != null){ + ctx.write(" ").write(ddlSyntax.getDropTableCascade()); + } + ctx.write(";").writeNewLine().writeNewLine(); + } + + public boolean visitBean(BeanDescriptor descriptor) { + + if (!descriptor.isInheritanceRoot()){ + return false; + } + + writeDropTable(descriptor); + + dropIntersectionTables(descriptor); + + return true; + } + + private void dropIntersectionTables(BeanDescriptor descriptor) { + + BeanPropertyAssocMany[] manyProps = descriptor.propertiesMany(); + for (int i = 0; i < manyProps.length; i++) { + if (manyProps[i].isManyToMany()){ + String intTable = manyProps[i].getIntersectionTableJoin().getTable(); + if (ctx.isProcessIntersectionTable(intTable)) { + writeDropTable(intTable); + } + } + } + } + + public void visitBeanEnd(BeanDescriptor descriptor) { + } + + public void visitBegin() { + + if (ddlSyntax.getDisableReferentialIntegrity() != null){ + ctx.write(ddlSyntax.getDisableReferentialIntegrity()); + ctx.write(";").writeNewLine().writeNewLine(); + } + } + + public void visitEnd() { + if (ddlSyntax.getEnableReferentialIntegrity() != null){ + ctx.write(ddlSyntax.getEnableReferentialIntegrity()); + ctx.write(";").writeNewLine().writeNewLine(); + } + } + + public PropertyVisitor visitProperty(BeanProperty p) { + // Return null as we are not interested in properties + return null; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/PropertyVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/PropertyVisitor.java new file mode 100644 index 000000000..d4f5834a0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/PropertyVisitor.java @@ -0,0 +1,53 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; + +/** + * Used to visit a BeanProperty given the type of bean property it is. + */ +public interface PropertyVisitor { + + /** + * Visit a OneToMany or ManyToMany property. + */ + public void visitMany(BeanPropertyAssocMany p); + + /** + * Visit the imported side of a OneToOne property. + */ + public void visitOneImported(BeanPropertyAssocOne p); + + /** + * Visit the exported side of a OneToOne property. + */ + public void visitOneExported(BeanPropertyAssocOne p); + + /** + * Visit an embedded property. + */ + public void visitEmbedded(BeanPropertyAssocOne p); + + /** + * Visit the scalar property of an embedded bean. + */ + public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded); + + /** + * Visit a scalar property. + */ + public void visitScalar(BeanProperty p); + + /** + * Visit a compound value object. + */ + public void visitCompound(BeanPropertyCompound p); + + /** + * Visit the scalar value inside a compound value object. + */ + public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/VisitorUtil.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/VisitorUtil.java new file mode 100644 index 000000000..3d5341382 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/VisitorUtil.java @@ -0,0 +1,125 @@ +package com.avaje.ebeaninternal.server.ddl; + +import java.util.List; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; + +/** + * Utility object to use BeanVisitor. + */ +public class VisitorUtil { + + /** + * Visit all the descriptors for a given server. + */ + public static void visit(SpiEbeanServer server, BeanVisitor visitor){ + + visit(server.getBeanDescriptors(), visitor); + } + + + /** + * Visit all the descriptors in the list. + */ + public static void visit(List> descriptors, BeanVisitor visitor){ + + visitor.visitBegin(); + + for (BeanDescriptor desc : descriptors) { + + if (desc.getBaseTable() != null){ + visitBean(desc, visitor); + } + } + + visitor.visitEnd(); + } + + /** + * Visit the bean using a visitor. + */ + public static void visitBean(BeanDescriptor desc, BeanVisitor visitor) { + + if (visitor.visitBean(desc)) { + + BeanProperty[] propertiesId = desc.propertiesId(); + for (int i = 0; i < propertiesId.length; i++) { + visit(visitor, propertiesId[i]); + } + BeanPropertyAssocOne unidirectional = desc.getUnidirectional(); + if (unidirectional != null){ + visit(visitor, unidirectional); + } + BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient(); + for (int i = 0; i < propertiesNonTransient.length; i++) { + BeanProperty p = propertiesNonTransient[i]; + if (!p.isFormula() && !p.isSecondaryTable()){ + visit(visitor, p); + } + } + + visitor.visitBeanEnd(desc); + } + } + + private static void visit(BeanVisitor visitor, BeanProperty p) { + PropertyVisitor pv = visitor.visitProperty(p); + if (pv != null){ + visit(p, pv); + } + } + + /** + * Visit all the properties. + */ + public static void visit(BeanProperty[] p, PropertyVisitor pv){ + + for (int i = 0; i < p.length; i++) { + visit(p[i], pv); + } + } + + /** + * Visit the property. + */ + public static void visit(BeanProperty p, PropertyVisitor pv) { + + if (p instanceof BeanPropertyAssocMany){ + pv.visitMany((BeanPropertyAssocMany)p); + + } else if (p instanceof BeanPropertyAssocOne){ + BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne)p; + if (assocOne.isEmbedded()){ + pv.visitEmbedded(assocOne); + BeanProperty[] embProps = assocOne.getProperties(); + for (int i = 0; i < embProps.length; i++) { + pv.visitEmbeddedScalar(embProps[i], assocOne); + } + + + } else if (assocOne.isOneToOneExported()){ + pv.visitOneExported(assocOne); + + } else { + pv.visitOneImported(assocOne); + + } + } else if (p instanceof BeanPropertyCompound){ + BeanPropertyCompound compound = (BeanPropertyCompound)p; + pv.visitCompound(compound); + + BeanProperty[] properties = compound.getScalarProperties(); + for (int i = 0; i < properties.length; i++) { + pv.visitCompoundScalar(compound, properties[i]); + } + + } else { + pv.visitScalar(p); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/package-info.java new file mode 100644 index 000000000..4dff5126c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/package-info.java @@ -0,0 +1,4 @@ +/** + * DDL generation. + */ +package com.avaje.ebeaninternal.server.ddl; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java new file mode 100644 index 000000000..5caab69c7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCascadeInfo.java @@ -0,0 +1,141 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import javax.persistence.CascadeType; + +/** + * Persist info for determining if save or delete should be performed. + *

+ * This is set to associated Beans, Table joins and List. + *

+ */ +public class BeanCascadeInfo { + + /** + * should delete cascade. + */ + boolean delete; + + /** + * Should save cascade. + */ + boolean save; + + /** + * Should validate cascade. + */ + boolean validate; + + /** + * Set the raw deployment attribute. + */ + public void setAttribute(String attr) { + if (attr == null){ + return; + } + attr = attr.toLowerCase(); + delete = (attr.indexOf("delete")>-1); + if (!delete){ + // same as EJB3 remove + delete = (attr.indexOf("remove")>-1); + } + save = (attr.indexOf("save")>-1); + if (!save){ + // same as EJB3 persist + save = (attr.indexOf("persist")>-1); + } + if (attr.indexOf("validate")>-1){ + validate = true; + } + + if (attr.indexOf("all")>-1){ + delete = true; + save = true; + validate = true; + } + } + + public void setTypes(CascadeType[] types) { + for (int i = 0; i < types.length; i++) { + setType(types[i]); + } + } + + private void setType(CascadeType type) { + if (type.equals(CascadeType.ALL)){ + save = true; + delete = true; + } + if (type.equals(CascadeType.REMOVE)){ + delete = true; + } + if (type.equals(CascadeType.PERSIST)){ + save = true; + } + if (type.equals(CascadeType.MERGE)){ + save = true; + } + if (save || delete){ + validate = true; + } + } + + /** + * Return true if delete should cascade. + */ + public boolean isDelete() { + return delete; + } + /** + * Set to true if delete should cascade. + */ + public void setDelete(boolean isDelete) { + this.delete = isDelete; + } + /** + * Return true if save should cascade. + */ + public boolean isSave() { + return save; + } + + /** + * Set to true if save should cascade. + */ + public void setSave(boolean isUpdate) { + this.save = isUpdate; + } + + /** + * Return true if validate should be cascaded. + */ + public boolean isValidate() { + return validate; + } + + /** + * Set validate to cascade or not. + */ + public void setValidate(boolean isValidate) { + this.validate = isValidate; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelp.java new file mode 100644 index 000000000..e7f88b754 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelp.java @@ -0,0 +1,73 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; + +/** + * Helper functions for performing tasks on Lists Sets or Maps. + */ +public interface BeanCollectionHelp { + + /** + * Set the EbeanServer that owns the configuration. + */ + public void setLoader(BeanCollectionLoader loader); + + /** + * Return the mechanism to add beans to the underlying collection. + *

+ * For Map's this needs to take the mapKey. + *

+ */ + public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey); + + /** + * Create an empty collection of the correct type. + */ + public Object createEmpty(boolean vanilla); + + /** + * Create an iterator for reading the entries. + */ + public Iterator getIterator(Object collection); + + /** + * Add a bean to the List Set or Map. + */ + public void add(BeanCollection collection, Object bean); + + /** + * Create a lazy loading proxy for a List Set or Map. + */ + public BeanCollection createReference(Object parentBean, String propertyName); + + /** + * Validate the List Set or Map. + */ + public ArrayList validate(Object manyValue); + + /** + * Refresh the List Set or Map. + */ + public void refresh(EbeanServer server, Query query, Transaction t, Object parentBean); + + /** + * Apply the new refreshed BeanCollection to the appropriate property of the parent bean. + */ + public void refresh(BeanCollection bc, Object parentBean); + + /** + * Write the collection out as json. + */ + public void jsonWrite(WriteJsonContext ctx, String name, Object collection, boolean explicitInclude); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java new file mode 100644 index 000000000..caee7c364 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java @@ -0,0 +1,49 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; + + +/** + * Creates Helpers specific to the type of the property (List Set or Map). + */ +public class BeanCollectionHelpFactory { + + /** + * Create the helper based on the many property. + */ + public static BeanCollectionHelp create(BeanPropertyAssocMany manyProperty) { + + ManyType manyType = manyProperty.getManyType(); + switch (manyType.getUnderlying()) { + case LIST: + return new BeanListHelp(manyProperty); + case SET: + return new BeanSetHelp(manyProperty); + case MAP: + return new BeanMapHelp(manyProperty); + default: + throw new RuntimeException("Invalid type "+manyType); + } + + } + + public static BeanCollectionHelp create(OrmQueryRequest request) { + + SpiQuery.Type manyType = request.getQuery().getType(); + + if (manyType.equals(SpiQuery.Type.LIST)){ + return new BeanListHelp(); + + } else if (manyType.equals(SpiQuery.Type.SET)) { + return new BeanSetHelp(); + + } else { + BeanDescriptor target = request.getBeanDescriptor(); + String mapKey = request.getQuery().getMapKey(); + return new BeanMapHelp(target, mapKey); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java new file mode 100644 index 000000000..ef2c127fc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -0,0 +1,2650 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.naming.InvalidNameException; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.ldap.LdapName; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.Query; +import com.avaje.ebean.Query.UseIndex; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.cache.ServerCache; +import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.config.EncryptKey; +import com.avaje.ebean.config.dbplatform.IdGenerator; +import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.text.TextException; +import com.avaje.ebean.text.json.JsonWriteBeanVisitor; +import com.avaje.ebean.validation.factory.Validator; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiUpdatePlan; +import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; +import com.avaje.ebeaninternal.server.cache.CachedBeanData; +import com.avaje.ebeaninternal.server.cache.CachedBeanDataFromBean; +import com.avaje.ebeaninternal.server.cache.CachedBeanDataToBean; +import com.avaje.ebeaninternal.server.cache.CachedBeanDataUpdate; +import com.avaje.ebeaninternal.server.cache.CachedManyIds; +import com.avaje.ebeaninternal.server.core.CacheOptions; +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists; +import com.avaje.ebeaninternal.server.el.ElComparator; +import com.avaje.ebeaninternal.server.el.ElComparatorCompound; +import com.avaje.ebeaninternal.server.el.ElComparatorProperty; +import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.ldap.LdapPersistenceException; +import com.avaje.ebeaninternal.server.persist.DmlUtil; +import com.avaje.ebeaninternal.server.query.CQueryPlan; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.reflect.BeanReflect; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext.ReadBeanState; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext.WriteBeanState; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.TypeManager; +import com.avaje.ebeaninternal.util.SortByClause; +import com.avaje.ebeaninternal.util.SortByClause.Property; +import com.avaje.ebeaninternal.util.SortByClauseParser; + +/** + * Describes Beans including their deployment information. + */ +public class BeanDescriptor { + + private static final Logger logger = Logger.getLogger(BeanDescriptor.class.getName()); + + private final ConcurrentHashMap updatePlanCache = new ConcurrentHashMap(); + + private final ConcurrentHashMap queryPlanCache = new ConcurrentHashMap(); + + private final ConcurrentHashMap elGetCache = new ConcurrentHashMap(); + + private final ConcurrentHashMap> comparatorCache = new ConcurrentHashMap>(); + + private final ConcurrentHashMap fkeyMap = new ConcurrentHashMap(); + + public enum EntityType { + ORM, EMBEDDED, SQL, META, LDAP, XMLELEMENT + } + + /** + * The EbeanServer name. Same as the plugin name. + */ + private final String serverName; + + /** + * Set to true if this is a LDAP domain object. + */ + private final EntityType entityType; + + /** + * Type of Identity generation strategy used. + */ + private final IdType idType; + + private final IdGenerator idGenerator; + + /** + * The database sequence name (optional). + */ + private final String sequenceName; + + private final String ldapBaseDn; + private final String[] ldapObjectclasses; + + /** + * SQL used to return last inserted id. Used for Identity columns where + * getGeneratedKeys is not supported. + */ + private final String selectLastInsertedId; + + private final boolean autoFetchTunable; + + /** + * Flag indicating this bean has no relationships. + */ + private final boolean cacheSharableBeans; + + private final String lazyFetchIncludes; + + /** + * The concurrency mode for beans of this type. + */ + private final ConcurrencyMode concurrencyMode; + + /** + * The tables this bean is dependent on. + */ + private final String[] dependantTables; + + private final CompoundUniqueContraint[] compoundUniqueConstraints; + + /** + * Extra deployment attributes. + */ + private final Map extraAttrMap; + + /** + * The base database table. + */ + private final String baseTable; + + /** + * Used to provide mechanism to new EntityBean instances. Generated code + * faster than reflection at this stage. + */ + private final BeanReflect beanReflect; + + /** + * Map of BeanProperty Linked so as to preserve order. + */ + private final LinkedHashMap propMap; + private final LinkedHashMap propMapByDbColumn; + + /** + * The type of bean this describes. + */ + private final Class beanType; + + /** + * This is not sent to a remote client. + */ + private final BeanDescriptorMap owner; + + /** + * The EntityBean type used to create new EntityBeans. + */ + private final Class factoryType; + + private final boolean enhancedBean; + + /** + * Intercept pre post on insert,update,delete and postLoad(). Server side + * only. + */ + private volatile BeanPersistController persistController; + + /** + * Listens for post commit insert update and delete events. + */ + private volatile BeanPersistListener persistListener; + + private volatile BeanQueryAdapter queryAdapter; + + /** + * If set overrides the find implementation. Server side only. + */ + private final BeanFinder beanFinder; + + /** + * The table joins for this bean. + */ + private final TableJoin[] derivedTableJoins; + + /** + * Inheritance information. Server side only. + */ + private final InheritInfo inheritInfo; + + /** + * Derived list of properties that make up the unique id. + */ + private final BeanProperty[] propertiesId; + + /** + * Derived list of properties that are used for version concurrency checking. + */ + private final BeanProperty[] propertiesVersion; + private final BeanProperty propertiesNaturalKey; + + /** + * Properties local to this type (not from a super type). + */ + private final BeanProperty[] propertiesLocal; + + private final BeanPropertyAssocOne unidirectional; + + /** + * A hashcode of all the many property names. This is used to efficiently + * create sets of loaded property names (for partial objects). + */ + private final int namesOfManyPropsHash; + + /** + * The set of names of the many properties. + */ + private final Set namesOfManyProps; + + /** + * list of properties that are Lists/Sets/Maps (Derived). + */ + private final BeanProperty[] propertiesNonMany; + private final BeanPropertyAssocMany[] propertiesMany; + private final BeanPropertyAssocMany[] propertiesManySave; + private final BeanPropertyAssocMany[] propertiesManyDelete; + private final BeanPropertyAssocMany[] propertiesManyToMany; + + /** + * list of properties that are associated beans and not embedded (Derived). + */ + private final BeanPropertyAssocOne[] propertiesOne; + + private final BeanPropertyAssocOne[] propertiesOneImported; + private final BeanPropertyAssocOne[] propertiesOneImportedSave; + private final BeanPropertyAssocOne[] propertiesOneImportedDelete; + + private final BeanPropertyAssocOne[] propertiesOneExported; + private final BeanPropertyAssocOne[] propertiesOneExportedSave; + private final BeanPropertyAssocOne[] propertiesOneExportedDelete; + + /** + * list of properties that are embedded beans. + */ + private final BeanPropertyAssocOne[] propertiesEmbedded; + + /** + * List of the scalar properties excluding id and secondary table properties. + */ + private final BeanProperty[] propertiesBaseScalar; + private final BeanPropertyCompound[] propertiesBaseCompound; + + private final BeanProperty[] propertiesTransient; + + /** + * All non transient properties excluding the id properties. + */ + final BeanProperty[] propertiesNonTransient; + + /** + * Set to true if the bean has version properties or an embedded bean has + * version properties. + */ + private final BeanProperty propertyFirstVersion; + + /** + * Set when the Id property is a single non-embedded property. Can make life + * simpler for this case. + */ + private final BeanProperty propertySingleId; + + /** + * The bean class name or the table name for MapBeans. + */ + private final String fullName; + + private final Map namedQueries; + + private final Map namedUpdates; + + /** + * Has local validation rules. + */ + private final boolean hasLocalValidation; + + /** + * Has local or recursive validation rules. + */ + private final boolean hasCascadeValidation; + + /** + * Properties with local validation rules. + */ + private final BeanProperty[] propertiesValidationLocal; + + /** + * Properties with local or cascade validation rules. + */ + private final BeanProperty[] propertiesValidationCascade; + + private final Validator[] beanValidators; + + /** + * Flag used to determine if saves can be skipped. + */ + private boolean saveRecurseSkippable; + + /** + * Flag used to determine if deletes can be skipped. + */ + private boolean deleteRecurseSkippable; + + /** + * Make the TypeManager available for helping SqlSelect. + */ + private final TypeManager typeManager; + + private final IdBinder idBinder; + + private String idBinderInLHSSql; + + private String idBinderIdSql; + + private String deleteByIdSql; + + private String deleteByIdInSql; + + private final String name; + + private final String baseTableAlias; + + /** + * If true then only changed properties get updated. + */ + private final boolean updateChangesOnly; + + private final ServerCacheManager cacheManager; + + private final CacheOptions cacheOptions; + + private final String defaultSelectClause; + private final Set defaultSelectClauseSet; + private final String[] defaultSelectDbArray; + + private final String descriptorId; + + private final UseIndex useIndex; + + private SpiEbeanServer ebeanServer; + + private ServerCache beanCache; + private ServerCache naturalKeyCache; + private ServerCache queryCache; + + /** + * Construct the BeanDescriptor. + */ + public BeanDescriptor(BeanDescriptorMap owner, TypeManager typeManager, DeployBeanDescriptor deploy, String descriptorId) { + + this.owner = owner; + this.cacheManager = owner.getCacheManager(); + this.serverName = owner.getServerName(); + this.entityType = deploy.getEntityType(); + this.name = InternString.intern(deploy.getName()); + this.baseTableAlias = "t0"; + this.fullName = InternString.intern(deploy.getFullName()); + this.descriptorId = descriptorId; + + this.useIndex = deploy.getUseIndex(); + this.typeManager = typeManager; + this.beanType = deploy.getBeanType(); + this.factoryType = deploy.getFactoryType(); + this.enhancedBean = beanType.equals(factoryType); + this.namedQueries = deploy.getNamedQueries(); + this.namedUpdates = deploy.getNamedUpdates(); + + this.inheritInfo = deploy.getInheritInfo(); + + this.beanFinder = deploy.getBeanFinder(); + this.persistController = deploy.getPersistController(); + this.persistListener = deploy.getPersistListener(); + this.queryAdapter = deploy.getQueryAdapter(); + this.cacheOptions = deploy.getCacheOptions(); + + this.defaultSelectClause = deploy.getDefaultSelectClause(); + this.defaultSelectClauseSet = deploy.parseDefaultSelectClause(defaultSelectClause); + this.defaultSelectDbArray = deploy.getDefaultSelectDbArray(defaultSelectClauseSet); + + this.idType = deploy.getIdType(); + this.idGenerator = deploy.getIdGenerator(); + this.ldapBaseDn = deploy.getLdapBaseDn(); + this.ldapObjectclasses = deploy.getLdapObjectclasses(); + this.sequenceName = deploy.getSequenceName(); + this.selectLastInsertedId = deploy.getSelectLastInsertedId(); + this.lazyFetchIncludes = InternString.intern(deploy.getLazyFetchIncludes()); + this.concurrencyMode = deploy.getConcurrencyMode(); + this.updateChangesOnly = deploy.isUpdateChangesOnly(); + + this.dependantTables = deploy.getDependantTables(); + this.compoundUniqueConstraints = deploy.getCompoundUniqueConstraints(); + + this.extraAttrMap = deploy.getExtraAttributeMap(); + + this.baseTable = InternString.intern(deploy.getBaseTable()); + + this.beanReflect = deploy.getBeanReflect(); + + this.autoFetchTunable = EntityType.ORM.equals(entityType) && (beanFinder == null); + + // helper object used to derive lists of properties + DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy); + + this.propMap = listHelper.getPropertyMap(); + this.propMapByDbColumn = getReverseMap(propMap); + this.propertiesTransient = listHelper.getTransients(); + this.propertiesNonTransient = listHelper.getNonTransients(); + this.propertiesBaseScalar = listHelper.getBaseScalar(); + this.propertiesBaseCompound = listHelper.getBaseCompound(); + this.propertiesId = listHelper.getId(); + this.propertiesNaturalKey = listHelper.getNaturalKey(); + this.propertiesVersion = listHelper.getVersion(); + this.propertiesEmbedded = listHelper.getEmbedded(); + this.propertiesLocal = listHelper.getLocal(); + this.unidirectional = listHelper.getUnidirectional(); + this.propertiesOne = listHelper.getOnes(); + this.propertiesOneExported = listHelper.getOneExported(); + this.propertiesOneExportedSave = listHelper.getOneExportedSave(); + this.propertiesOneExportedDelete = listHelper.getOneExportedDelete(); + this.propertiesOneImported = listHelper.getOneImported(); + this.propertiesOneImportedSave = listHelper.getOneImportedSave(); + this.propertiesOneImportedDelete = listHelper.getOneImportedDelete(); + + this.propertiesMany = listHelper.getMany(); + this.propertiesNonMany = listHelper.getNonMany(); + this.propertiesManySave = listHelper.getManySave(); + this.propertiesManyDelete = listHelper.getManyDelete(); + this.propertiesManyToMany = listHelper.getManyToMany(); + boolean noRelationships = propertiesOne.length + propertiesMany.length == 0; + this.cacheSharableBeans = noRelationships && cacheOptions.isReadOnly(); + + this.namesOfManyProps = deriveManyPropNames(); + this.namesOfManyPropsHash = namesOfManyProps.hashCode(); + + this.derivedTableJoins = listHelper.getTableJoin(); + this.propertyFirstVersion = listHelper.getFirstVersion(); + + if (propertiesId.length == 1) { + this.propertySingleId = propertiesId[0]; + } else { + this.propertySingleId = null; + } + + // 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 + // well. We do that later in initialiseOther(). + + saveRecurseSkippable = (0 == (propertiesOneExportedSave.length + propertiesOneImportedSave.length + propertiesManySave.length)); + + // Check if there are no cascade delete associated beans (also subject to + // change in initialiseOther()). + deleteRecurseSkippable = (0 == (propertiesOneExportedDelete.length + propertiesOneImportedDelete.length + propertiesManyDelete.length)); + + this.propertiesValidationLocal = listHelper.getPropertiesWithValidators(false); + this.propertiesValidationCascade = listHelper.getPropertiesWithValidators(true); + this.beanValidators = listHelper.getBeanValidators(); + this.hasLocalValidation = (propertiesValidationLocal.length > 0 || beanValidators.length > 0); + this.hasCascadeValidation = (propertiesValidationCascade.length > 0 || beanValidators.length > 0); + + // object used to handle Id values + this.idBinder = owner.createIdBinder(propertiesId); + } + + private LinkedHashMap getReverseMap(LinkedHashMap propMap) { + + LinkedHashMap revMap = new LinkedHashMap(propMap.size() * 2); + + for (BeanProperty prop : propMap.values()) { + if (prop.getDbColumn() != null) { + revMap.put(prop.getDbColumn(), prop); + } + } + + return revMap; + } + + /** + * Set the server. Primarily so that the Many's can lazy load. + */ + public void setEbeanServer(SpiEbeanServer ebeanServer) { + this.ebeanServer = ebeanServer; + for (int i = 0; i < propertiesMany.length; i++) { + // used for creating lazy loading lists etc + propertiesMany[i].setLoader(ebeanServer); + } + } + + /** + * Determine the concurrency mode based on the existence of a non-null version + * property value. + */ + public ConcurrencyMode determineConcurrencyMode(Object bean) { + + if (propertyFirstVersion == null) { + return ConcurrencyMode.NONE; + } + Object v = propertyFirstVersion.getValue(bean); + return (v == null) ? ConcurrencyMode.NONE : ConcurrencyMode.VERSION; + } + + /** + * Return the Set of embedded beans that have changed. + */ + public Set getDirtyEmbeddedProperties(Object bean) { + + HashSet dirtyProperties = null; + + for (int i = 0; i < propertiesEmbedded.length; i++) { + Object embValue = propertiesEmbedded[i].getValue(bean); + if (embValue instanceof EntityBean) { + if (((EntityBean) embValue)._ebean_getIntercept().isDirty()) { + // this embedded is dirty so should be included in an update + if (dirtyProperties == null) { + dirtyProperties = new HashSet(); + } + dirtyProperties.add(propertiesEmbedded[i].getName()); + } + } else { + // must assume it is dirty + if (dirtyProperties == null) { + dirtyProperties = new HashSet(); + } + dirtyProperties.add(propertiesEmbedded[i].getName()); + } + } + + return dirtyProperties; + } + + /** + * Determine the non-null properties of the bean. + */ + public Set determineLoadedProperties(Object bean) { + + HashSet nonNullProps = new HashSet(); + + for (int j = 0; j < propertiesId.length; j++) { + if (propertiesId[j].getValue(bean) != null) { + nonNullProps.add(propertiesId[j].getName()); + } + } + for (int i = 0; i < propertiesNonTransient.length; i++) { + if (propertiesNonTransient[i].getValue(bean) != null) { + nonNullProps.add(propertiesNonTransient[i].getName()); + } + } + return nonNullProps; + } + + /** + * Return the EbeanServer instance that owns this BeanDescriptor. + */ + public SpiEbeanServer getEbeanServer() { + return ebeanServer; + } + + /** + * Return the type of this domain object. + */ + public EntityType getEntityType() { + return entityType; + } + + /** + * Return the default strategy for using a lucene index (if an index is + * defined on this bean type). + */ + public UseIndex getUseIndex() { + return useIndex; + } + + /** + * Initialise the Id properties first. + *

+ * These properties need to be initialised prior to the association properties + * as they are used to get the imported and exported properties. + *

+ */ + public void initialiseId() { + + if (logger.isLoggable(Level.FINER)) { + logger.finer("BeanDescriptor initialise " + fullName); + } + + if (inheritInfo != null) { + inheritInfo.setDescriptor(this); + } + + if (isEmbedded()) { + // initialise all the properties + Iterator it = propertiesAll(); + while (it.hasNext()) { + BeanProperty prop = it.next(); + prop.initialise(); + } + } else { + // initialise just the Id properties + BeanProperty[] idProps = propertiesId(); + for (int i = 0; i < idProps.length; i++) { + idProps[i].initialise(); + } + } + } + + /** + * Initialise the exported and imported parts for associated properties. + */ + public void initialiseOther() { + + if (!isEmbedded()) { + // initialise all the non-id properties + Iterator it = propertiesAll(); + while (it.hasNext()) { + BeanProperty prop = it.next(); + if (!prop.isId()) { + prop.initialise(); + } + } + } + + if (unidirectional != null) { + unidirectional.initialise(); + } + + idBinder.initialise(); + idBinderInLHSSql = idBinder.getBindIdInSql(baseTableAlias); + idBinderIdSql = idBinder.getBindIdSql(baseTableAlias); + String idBinderInLHSSqlNoAlias = idBinder.getBindIdInSql(null); + String idEqualsSql = idBinder.getBindIdSql(null); + + deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql; + deleteByIdInSql = "delete from " + baseTable + " where " + idBinderInLHSSqlNoAlias + " "; + + if (!isEmbedded()) { + // parse every named update up front into sql dml + for (DeployNamedUpdate namedUpdate : namedUpdates.values()) { + DeployUpdateParser parser = new DeployUpdateParser(this); + namedUpdate.initialise(parser); + } + } + + } + + public void initInheritInfo() { + if (inheritInfo != null) { + // need to check every BeanDescriptor in the inheritance hierarchy + if (saveRecurseSkippable) { + saveRecurseSkippable = inheritInfo.isSaveRecurseSkippable(); + } + if (deleteRecurseSkippable) { + deleteRecurseSkippable = inheritInfo.isDeleteRecurseSkippable(); + } + } + } + + /** + * Initialise the cache once the server has started. + */ + public void cacheInitialise() { + if (cacheOptions.isUseNaturalKeyCache()) { + this.naturalKeyCache = cacheManager.getNaturalKeyCache(beanType); + } + if (cacheOptions.isUseCache()) { + this.beanCache = cacheManager.getBeanCache(beanType); + } + } + + protected boolean hasInheritance() { + return inheritInfo != null; + } + + protected boolean isDynamicSubclass() { + return !beanType.equals(factoryType); + } + + /** + * Set the LDAP objectClasses to the attributes. + */ + public void setLdapObjectClasses(Attributes attributes) { + + if (ldapObjectclasses != null) { + BasicAttribute ocAttrs = new BasicAttribute("objectclass"); + for (int i = 0; i < ldapObjectclasses.length; i++) { + ocAttrs.add(ldapObjectclasses[i]); + } + attributes.put(ocAttrs); + } + } + + /** + * Creates Attributes with the objectclass. + */ + public Attributes createAttributes() { + + Attributes attrs = new BasicAttributes(true); + setLdapObjectClasses(attrs); + return attrs; + } + + public String getLdapBaseDn() { + return ldapBaseDn; + } + + public LdapName createLdapNameById(Object id) throws InvalidNameException { + + LdapName baseDn = new LdapName(ldapBaseDn); + idBinder.createLdapNameById(baseDn, id); + return baseDn; + } + + public LdapName createLdapName(Object bean) { + + try { + LdapName name = new LdapName(ldapBaseDn); + if (bean != null) { + idBinder.createLdapNameByBean(name, bean); + } + return name; + + } catch (InvalidNameException e) { + throw new LdapPersistenceException(e); + } + } + + public SqlUpdate deleteById(Object id, List idList) { + if (id != null) { + return deleteById(id); + } else { + return deleteByIdList(idList); + } + } + + /** + * Return SQL that can be used to delete a list of Id's without any optimistic + * concurrency checking. + */ + private SqlUpdate deleteByIdList(List idList) { + + StringBuilder sb = new StringBuilder(deleteByIdInSql); + String inClause = idBinder.getIdInValueExprDelete(idList.size()); + sb.append(inClause); + + DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString()); + for (int i = 0; i < idList.size(); i++) { + idBinder.bindId(delete, idList.get(i)); + } + return delete; + } + + /** + * Return SQL that can be used to delete by Id without any optimistic + * concurrency checking. + */ + private SqlUpdate deleteById(Object id) { + + DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByIdSql); + + Object[] bindValues = idBinder.getBindValues(id); + for (int i = 0; i < bindValues.length; i++) { + sqlDelete.addParameter(bindValues[i]); + } + + return sqlDelete; + } + + /** + * Add objects to ElPropertyDeploy etc. These are used so that expressions on + * foreign keys don't require an extra join. + */ + public void add(BeanFkeyProperty fkey) { + fkeyMap.put(fkey.getName(), fkey); + } + + public void initialiseFkeys() { + for (int i = 0; i < propertiesOneImported.length; i++) { + propertiesOneImported[i].addFkey(); + } + } + + public boolean calculateUseCache(Boolean queryUseCache) { + return (queryUseCache != null) ? queryUseCache.booleanValue() : isBeanCaching(); + } + + public boolean calculateUseNaturalKeyCache(Boolean queryUseCache) { + return (queryUseCache != null) ? queryUseCache.booleanValue() : isBeanCaching(); + } + + /** + * Return the cache options. + */ + public CacheOptions getCacheOptions() { + return cacheOptions; + } + + /** + * Return the Encrypt key given the BeanProperty. + */ + public EncryptKey getEncryptKey(BeanProperty p) { + return owner.getEncryptKey(baseTable, p.getDbColumn()); + } + + /** + * Return the Encrypt key given the table and column name. + */ + public EncryptKey getEncryptKey(String tableName, String columnName) { + return owner.getEncryptKey(tableName, columnName); + } + + /** + * Execute the warming cache query (if defined) and load the cache. + */ + public void runCacheWarming() { + if (cacheOptions == null) { + return; + } + String warmingQuery = cacheOptions.getWarmingQuery(); + if (warmingQuery != null && warmingQuery.trim().length() > 0) { + Query query = ebeanServer.createQuery(beanType, warmingQuery); + query.setUseCache(true); + query.setReadOnly(true); + query.setLoadBeanCache(true); + List list = query.findList(); + if (logger.isLoggable(Level.INFO)) { + String msg = "Loaded " + beanType + " cache with [" + list.size() + "] beans"; + logger.info(msg); + } + } + } + + /** + * Return true if this bean type has a default select clause that is not + * simply select all properties. + */ + public boolean hasDefaultSelectClause() { + return defaultSelectClause != null; + } + + /** + * Return the default select clause. + */ + public String getDefaultSelectClause() { + return defaultSelectClause; + } + + /** + * Return the default select clause already parsed into an ordered Set. + */ + public Set getDefaultSelectClauseSet() { + return defaultSelectClauseSet; + } + + /** + * For LDAP return array of (DB) attributes to include in query by default. + */ + public String[] getDefaultSelectDbArray() { + return defaultSelectDbArray; + } + + /** + * Return true if this object is the root level object in its entity + * inheritance. + */ + public boolean isInheritanceRoot() { + return inheritInfo == null || inheritInfo.isRoot(); + } + + /** + * Return true if there is currently query caching for this type of bean. + */ + public boolean isQueryCaching() { + return queryCache != null; + } + + /** + * Return true if there is currently bean caching for this type of bean. + */ + public boolean isBeanCaching() { + return beanCache != null; + } + + public boolean cacheIsUseManyId() { + return isBeanCaching(); + } + + /** + * Return true if the persist request needs to notify the cache. + */ + public boolean isCacheNotify() { + + if (isBeanCaching() || isQueryCaching()) { + return true; + } + for (int i = 0; i < propertiesOneImported.length; i++) { + if (propertiesOneImported[i].getTargetDescriptor().isBeanCaching()) { + return true; + } + } + return false; + } + + /** + * Return true if there is L2 caching (Lucene or Bean cache) for this bean + * type. + */ + public boolean isUsingL2Cache() { + return isBeanCaching(); + } + + /** + * Invalidate parts of cache due to SqlUpdate or external modification etc. + */ + public void cacheNotify(TableIUD tableIUD) { + // inserts don't invalidate the bean cache + if (tableIUD.isUpdateOrDelete()) { + cacheClear(); + } + // any change invalidates the query cache + queryCacheClear(); + } + + /** + * Clear the query cache. + */ + public void queryCacheClear() { + if (queryCache != null) { + queryCache.clear(); + } + } + + /** + * Get a query result from the query cache. + */ + @SuppressWarnings("unchecked") + public BeanCollection queryCacheGet(Object id) { + if (queryCache == null) { + return null; + } else { + return (BeanCollection) queryCache.get(id); + } + } + + /** + * Put a query result into the query cache. + */ + public void queryCachePut(Object id, BeanCollection query) { + if (queryCache == null) { + queryCache = cacheManager.getQueryCache(beanType); + } + queryCache.put(id, query); + } + + private ServerCache getBeanCache() { + if (beanCache == null) { + beanCache = cacheManager.getBeanCache(beanType); + } + return beanCache; + } + + /** + * Clear the bean cache. + */ + public void cacheClear() { + if (beanCache != null) { + beanCache.clear(); + } + } + + /** + * Put a bean into the bean cache. + */ + public void cachePutBeanData(Object bean) { + + CachedBeanData beanData = CachedBeanDataFromBean.extract(this, bean); + + Object id = getId(bean); + getBeanCache().put(id, beanData); + if (beanData.isNaturalKeyUpdate() && naturalKeyCache != null) { + Object naturalKey = beanData.getNaturalKey(); + if (naturalKey != null) { + naturalKeyCache.put(naturalKey, id); + } + } + } + + public boolean cacheLoadMany(BeanPropertyAssocMany many, BeanCollection bc, Object parentId, Boolean readOnly, boolean vanilla) { + + CachedManyIds ids = cacheGetCachedManyIds(parentId, many.getName()); + if (ids == null) { + return false; + } + + Object ownerBean = bc.getOwnerBean(); + EntityBeanIntercept ebi = ((EntityBean) ownerBean)._ebean_getIntercept(); + PersistenceContext persistenceContext = ebi.getPersistenceContext(); + + BeanDescriptor targetDescriptor = many.getTargetDescriptor(); + + List idList = ids.getIdList(); + bc.checkEmptyLazyLoad(); + for (int i = 0; i < idList.size(); i++) { + Object id = idList.get(i); + Object refBean = targetDescriptor.createReference(vanilla, readOnly, id, null); + EntityBeanIntercept refEbi = ((EntityBean) refBean)._ebean_getIntercept(); + + many.add(bc, refBean); + persistenceContext.put(id, refBean); + refEbi.setPersistenceContext(persistenceContext); + } + return true; + } + + public void cachePutMany(BeanPropertyAssocMany many, BeanCollection bc, Object parentId) { + BeanDescriptor targetDescriptor = many.getTargetDescriptor(); + Collection actualDetails = bc.getActualDetails(); + ArrayList idList = new ArrayList(); + for (Object bean : actualDetails) { + Object id = targetDescriptor.getId(bean); + idList.add(id); + } + CachedManyIds ids = new CachedManyIds(idList); + cachePutCachedManyIds(parentId, many.getName(), ids); + } + + public void cacheRemoveCachedManyIds(Object parentId, String propertyName) { + ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName); + collectionIdsCache.remove(parentId); + } + + public void cacheClearCachedManyIds(String propertyName) { + ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName); + collectionIdsCache.clear(); + } + + public CachedManyIds cacheGetCachedManyIds(Object parentId, String propertyName) { + ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName); + return (CachedManyIds) collectionIdsCache.get(parentId); + } + + public void cachePutCachedManyIds(Object parentId, String propertyName, CachedManyIds ids) { + ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName); + collectionIdsCache.put(parentId, ids); + } + + /** + * Return a bean from the bean cache. + */ + @SuppressWarnings("unchecked") + public T cacheGetBean(Object id, boolean vanilla, Boolean readOnly) { + + CachedBeanData d = (CachedBeanData) getBeanCache().get(id); + if (d == null) { + return null; + } + if (cacheSharableBeans && !vanilla && !Boolean.FALSE.equals(readOnly)) { + Object bean = d.getSharableBean(); + if (bean != null) { + return (T) bean; + } + } + + T bean = (T) createBean(vanilla); + convertSetId(id, bean); + if (!vanilla && Boolean.TRUE.equals(readOnly)) { + ((EntityBean) bean)._ebean_getIntercept().setReadOnly(true); + } + + CachedBeanDataToBean.load(this, bean, d); + return bean; + } + + public boolean cacheIsNaturalKey(String propName) { + return propName != null && propName.equals(cacheOptions.getNaturalKey()); + } + + public Object cacheGetNaturalKeyId(Object uniqueKeyValue) { + if (naturalKeyCache != null) { + return naturalKeyCache.get(uniqueKeyValue); + } + return null; + } + + /** + * Remove a bean from the cache given its Id. + */ + public void cacheRemove(Object id) { + if (beanCache != null) { + beanCache.remove(id); + } + for (int i = 0; i < propertiesOneImported.length; i++) { + propertiesOneImported[i].cacheClear(); + } + } + + /** + * Remove a bean from the cache given its Id. + */ + public void cacheDelete(Object id, PersistRequestBean deleteRequest) { + if (beanCache != null) { + beanCache.remove(id); + } + for (int i = 0; i < propertiesOneImported.length; i++) { + BeanPropertyAssocMany many = propertiesOneImported[i].getRelationshipProperty(); + if (many != null) { + propertiesOneImported[i].cacheDelete(true, deleteRequest); + } + } + } + + public void cacheInsert(Object id, PersistRequestBean insertRequest) { + if (queryCache != null) { + queryCache.clear(); + } + for (int i = 0; i < propertiesOneImported.length; i++) { + propertiesOneImported[i].cacheDelete(false, insertRequest.getBean()); + } + } + + /** + * Update the cached bean data. + */ + public void cacheUpdate(Object id, PersistRequestBean updateRequest) { + + ServerCache cache = getBeanCache(); + CachedBeanData cd = (CachedBeanData) cache.get(id); + if (cd != null) { + CachedBeanData newCd = CachedBeanDataUpdate.update(this, cd, updateRequest); + cache.put(id, newCd); + if (newCd.isNaturalKeyUpdate() && naturalKeyCache != null) { + Object oldKey = propertiesNaturalKey.getValue(updateRequest.getOldValues()); + Object newKey = propertiesNaturalKey.getValue(updateRequest.getBean()); + if (oldKey != null) { + naturalKeyCache.remove(oldKey); + } + if (newKey != null) { + naturalKeyCache.put(newKey, id); + } + } + } + } + + /** + * Return the base table alias. This is always the first letter of the bean + * name. + */ + public String getBaseTableAlias() { + return baseTableAlias; + } + + public boolean loadFromCache(EntityBeanIntercept ebi) { + Object bean = ebi.getOwner(); + Object id = getId(bean); + + return loadFromCache(bean, ebi, id); + } + + public boolean loadFromCache(Object bean, EntityBeanIntercept ebi, Object id) { + + CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(id); + if (cacheData == null) { + return false; + } + String lazyLoadProperty = ebi.getLazyLoadProperty(); + if (lazyLoadProperty != null && !cacheData.containsProperty(lazyLoadProperty)) { + return false; + } + + CachedBeanDataToBean.load(this, bean, ebi, cacheData); + return true; + } + + public void preAllocateIds(int batchSize) { + if (idGenerator != null) { + idGenerator.preAllocateIds(batchSize); + } + } + + public Object nextId(Transaction t) { + if (idGenerator != null) { + return idGenerator.nextId(t); + } else { + return null; + } + } + + public DeployPropertyParser createDeployPropertyParser() { + return new DeployPropertyParser(this); + } + + /** + * Convert the logical orm update statement into sql by converting the bean + * properties and bean name to database columns and table. + */ + public String convertOrmUpdateToSql(String ormUpdateStatement) { + return new DeployUpdateParser(this).parse(ormUpdateStatement); + } + + /** + * Reset the statistics on all the query plans. + */ + public void clearQueryStatistics() { + Iterator it = queryPlanCache.values().iterator(); + while (it.hasNext()) { + CQueryPlan queryPlan = (CQueryPlan) it.next(); + queryPlan.resetStatistics(); + } + } + + /** + * Execute the postLoad if a BeanPersistController exists for this bean. + */ + @SuppressWarnings("unchecked") + public void postLoad(Object bean, Set includedProperties) { + BeanPersistController c = persistController; + if (c != null) { + c.postLoad((T) bean, includedProperties); + } + } + + /** + * Return the query plans for this BeanDescriptor. + */ + public Iterator queryPlans() { + return queryPlanCache.values().iterator(); + } + + public CQueryPlan getQueryPlan(Integer key) { + return queryPlanCache.get(key); + } + + public void putQueryPlan(Integer key, CQueryPlan plan) { + queryPlanCache.put(key, plan); + } + + /** + * Get a UpdatePlan for a given hash. + */ + public SpiUpdatePlan getUpdatePlan(Integer key) { + return updatePlanCache.get(key); + } + + /** + * Add a UpdatePlan to the cache with a given hash. + */ + public void putUpdatePlan(Integer key, SpiUpdatePlan plan) { + updatePlanCache.put(key, plan); + } + + /** + * Return the TypeManager. + */ + public TypeManager getTypeManager() { + return typeManager; + } + + /** + * Return true if updates should only include changed properties. Otherwise + * all loaded properties are included in the update. + */ + public boolean isUpdateChangesOnly() { + return updateChangesOnly; + } + + /** + * Return true if save does not recurse to other beans. That is return true if + * there are no assoc one or assoc many beans that cascade save. + */ + public boolean isSaveRecurseSkippable() { + return saveRecurseSkippable; + } + + /** + * Return true if delete does not recurse to other beans. That is return true + * if there are no assoc one or assoc many beans that cascade delete. + */ + public boolean isDeleteRecurseSkippable() { + return deleteRecurseSkippable; + } + + /** + * Return true if this type has local validation rules. + */ + public boolean hasLocalValidation() { + return hasLocalValidation; + } + + /** + * Return true if this type has local or cascading validation rules. + */ + public boolean hasCascadeValidation() { + return hasCascadeValidation; + } + + public InvalidValue validate(boolean cascade, Object bean) { + + if (!hasCascadeValidation) { + // no validation rules at all on this bean + return null; + } + + List errList = null; + + Set loadedProps = null; + if (bean instanceof EntityBean) { + EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept(); + loadedProps = ebi.getLoadedProps(); + } + if (loadedProps != null) { + // validate just the loaded properties + Iterator propIt = loadedProps.iterator(); + while (propIt.hasNext()) { + String propName = (String) propIt.next(); + BeanProperty property = getBeanProperty(propName); + + // check if we should fire validation on this property + if (property != null && property.hasValidationRules(cascade)) { + Object value = property.getValue(bean); + List errs = property.validate(cascade, value); + if (errs != null) { + if (errList == null) { + errList = new ArrayList(); + } + errList.addAll(errs); + } + } + } + } else { + // get appropriate list of properties with validation rules + BeanProperty[] props = cascade ? propertiesValidationCascade : propertiesValidationLocal; + + // validate all the properties + for (int i = 0; i < props.length; i++) { + BeanProperty prop = props[i]; + Object value = prop.getValue(bean); + List errs = prop.validate(cascade, value); + if (errs != null) { + if (errList == null) { + errList = new ArrayList(); + } + errList.addAll(errs); + } + } + } + + for (int i = 0; i < beanValidators.length; i++) { + if (!beanValidators[i].isValid(bean)) { + if (errList == null) { + errList = new ArrayList(); + } + Validator v = beanValidators[i]; + errList.add(new InvalidValue(v.getKey(), v.getAttributes(), getFullName(), null, bean)); + } + } + + if (errList == null) { + return null; + } + + return new InvalidValue(null, getFullName(), bean, InvalidValue.toArray(errList)); + } + + /** + * Return the many property included in the query or null if one is not. + */ + public BeanPropertyAssocMany getManyProperty(SpiQuery query) { + + OrmQueryDetail detail = query.getDetail(); + for (int i = 0; i < propertiesMany.length; i++) { + if (detail.includes(propertiesMany[i].getName())) { + return propertiesMany[i]; + } + } + + return null; + } + + /** + * Return the IdBinder which is helpful for handling the various types of Id. + */ + public IdBinder getIdBinder() { + return idBinder; + } + + /** + * Return the sql for binding an id. This is the columns with table alias that + * make up the id. + */ + public String getIdBinderIdSql() { + return idBinderIdSql; + } + + /** + * Return the sql for binding id's using an IN clause. + */ + public String getIdBinderInLHSSql() { + return idBinderInLHSSql; + } + + /** + * Bind the idValue to the preparedStatement. + *

+ * This takes care of the various id types such as embedded beans etc. + *

+ */ + public void bindId(DataBind dataBind, Object idValue) throws SQLException { + idBinder.bindId(dataBind, idValue); + } + + /** + * Return the id as an array of scalar bindable values. + *

+ * This 'flattens' any EmbeddedId or multiple Id property cases. + *

+ */ + public Object[] getBindIdValues(Object idValue) { + return idBinder.getBindValues(idValue); + } + + /** + * Return a named query. + */ + public DeployNamedQuery getNamedQuery(String name) { + return namedQueries.get(name); + } + + public DeployNamedQuery addNamedQuery(DeployNamedQuery deployNamedQuery) { + return namedQueries.put(deployNamedQuery.getName(), deployNamedQuery); + } + + /** + * Return a named update. + */ + public DeployNamedUpdate getNamedUpdate(String name) { + return namedUpdates.get(name); + } + + /** + * Create an EntityBean or "Vanilla" bean depending on the flag. + */ + public Object createBean(boolean vanillaMode) { + return vanillaMode ? createVanillaBean() : createEntityBean(); + } + + /** + * Create a plain vanilla object. + *

+ * Used for EmbeddedId Bean construction. + *

+ */ + public Object createVanillaBean() { + return beanReflect.createVanillaBean(); + } + + /** + * Creates a new EntityBean without using the creation queue. + */ + public EntityBean createEntityBean() { + try { + // Note factoryType is used indirectly via beanReflect + EntityBean eb = (EntityBean) beanReflect.createEntityBean(); + + return eb; + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + /** + * Create a reference bean based on the id. + */ + @SuppressWarnings("unchecked") + public T createReference(boolean vanillaMode, Boolean readOnly, Object id, Object parent) { + + if (cacheSharableBeans && !vanillaMode && !Boolean.FALSE.equals(readOnly)) { + CachedBeanData d = (CachedBeanData) getBeanCache().get(id); + if (d != null) { + Object shareableBean = d.getSharableBean(); + if (shareableBean != null) { + return (T) shareableBean; + } + } + } + try { + Object bean = createBean(vanillaMode); + + convertSetId(id, bean); + + if (!vanillaMode) { + EntityBean eb = (EntityBean) bean; + + EntityBeanIntercept ebi = eb._ebean_getIntercept(); + ebi.setBeanLoaderByServerName(ebeanServer.getName()); + + if (parent != null) { + // Special case for a OneToOne ... parent + // needs to be added to context prior to query + ebi.setParentBean(parent); + } + + // Note: not creating proxies for many's... + ebi.setReference(); + } + + return (T) bean; + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + /** + * Return the BeanProperty for the given deployment name. + */ + public BeanProperty getBeanPropertyFromDbColumn(String dbColumn) { + return propMapByDbColumn.get(dbColumn); + } + + /** + * Return the bean property traversing the object graph and taking into + * account inheritance. + */ + public BeanProperty getBeanPropertyFromPath(String path) { + + String[] split = SplitName.splitBegin(path); + if (split[1] == null) { + return _findBeanProperty(split[0]); + } + BeanPropertyAssoc assocProp = (BeanPropertyAssoc) _findBeanProperty(split[0]); + BeanDescriptor targetDesc = assocProp.getTargetDescriptor(); + + return targetDesc.getBeanPropertyFromPath(split[1]); + } + + /** + * Return the BeanDescriptor for a given path of Associated One or Many beans. + */ + public BeanDescriptor getBeanDescriptor(String path) { + if (path == null) { + return this; + } + String[] splitBegin = SplitName.splitBegin(path); + + BeanProperty beanProperty = propMap.get(splitBegin[0]); + if (beanProperty instanceof BeanPropertyAssoc) { + BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; + return assocProp.getTargetDescriptor().getBeanDescriptor(splitBegin[1]); + + } else { + throw new RuntimeException("Error getting BeanDescriptor for path " + path + " from " + getFullName()); + } + } + + /** + * Return the BeanDescriptor of another bean type. + */ + public BeanDescriptor getBeanDescriptor(Class otherType) { + return owner.getBeanDescriptor(otherType); + } + + /** + * Return the "shadow" property to support unidirectional relationships. + *

+ * For bidirectional this is a real property on the bean. For unidirectional + * relationships we have this 'shadow' property which is not externally + * visible. + *

+ */ + public BeanPropertyAssocOne getUnidirectional() { + if (unidirectional != null) { + return unidirectional; + } + if (inheritInfo != null && !inheritInfo.isRoot()) { + return inheritInfo.getParent().getBeanDescriptor().getUnidirectional(); + } + return null; + } + + /** + * Get a property value from a bean of this type. + */ + public Object getValue(Object bean, String property) { + return getBeanProperty(property).getValue(bean); + } + + /** + * Return true if this bean type should use IdGeneration. + *

+ * If this is false and the Id is null it is assumed that a database auto + * increment feature is being used to populate the id. + *

+ */ + public boolean isUseIdGenerator() { + return idGenerator != null; + } + + /** + * Return the alternate "Id" that identifies this BeanDescriptor. This is an + * alternative to using the bean class name. + */ + public String getDescriptorId() { + return descriptorId; + } + + /** + * Return the class type this BeanDescriptor describes. + */ + public Class getBeanType() { + return beanType; + } + + /** + * Return the class type this BeanDescriptor describes. + */ + public Class getFactoryType() { + return factoryType; + } + + /** + * Return the bean class name this descriptor is used for. + *

+ * If this BeanDescriptor is for a table then this returns the table name + * instead. + *

+ */ + public String getFullName() { + return fullName; + } + + /** + * Return the short name of the entity bean. + */ + public String getName() { + return name; + } + + /** + * Summary description. + */ + public String toString() { + return fullName; + } + + /** + * Helper method to return the unique property. If only one property makes up + * the unique id then it's value is returned. If there is a concatenated + * unique id then a Map is built with the keys being the names of the + * properties that make up the unique id. + */ + public Object getId(Object bean) { + + if (propertySingleId != null) { + if (inheritInfo != null && !enhancedBean) { + // avoid generated method via forced reflection use + return propertySingleId.getValueViaReflection(bean); + + } else { + return propertySingleId.getValue(bean); + } + } + + // it is a concatenated id Not embedded + // so return a Map + LinkedHashMap idMap = new LinkedHashMap(); + for (int i = 0; i < propertiesId.length; i++) { + + Object value = propertiesId[i].getValue(bean); + idMap.put(propertiesId[i].getName(), value); + } + return idMap; + } + + /** + * Return false if the id is a simple scalar and false if it is embedded or + * concatenated. + */ + public boolean isComplexId() { + return idBinder.isComplexId(); + } + + /** + * Return the default order by that may need to be added if a many property is + * included in the query. + */ + public String getDefaultOrderBy() { + return idBinder.getDefaultOrderBy(); + } + + /** + * Convert the type of the idValue if required. + */ + public Object convertId(Object idValue) { + return idBinder.convertSetId(idValue, null); + } + + /** + * Convert and set the id value. + *

+ * If the bean is not null, the id value is set to the id property of the bean + * after it has been converted to the correct type. + *

+ */ + public Object convertSetId(Object idValue, Object bean) { + return idBinder.convertSetId(idValue, bean); + } + + /** + * Get a BeanProperty by its name. + */ + public BeanProperty getBeanProperty(String propName) { + return (BeanProperty) propMap.get(propName); + } + + public void sort(List list, String sortByClause) { + + ElComparator comparator = getElComparator(sortByClause); + Collections.sort(list, comparator); + } + + public ElComparator getElComparator(String propNameOrSortBy) { + ElComparator c = comparatorCache.get(propNameOrSortBy); + if (c == null) { + c = createComparator(propNameOrSortBy); + comparatorCache.put(propNameOrSortBy, c); + } + return c; + } + + /** + * 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. + */ + public boolean lazyLoadMany(EntityBeanIntercept ebi) { + + String lazyLoadProperty = ebi.getLazyLoadProperty(); + BeanProperty lazyLoadBeanProp = getBeanProperty(lazyLoadProperty); + + if (lazyLoadBeanProp instanceof BeanPropertyAssocMany) { + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) lazyLoadBeanProp; + manyProp.createReference(ebi.getOwner()); + Set loadedProps = ebi.getLoadedProps(); + HashSet newLoadedProps = new HashSet(); + if (loadedProps != null) { + newLoadedProps.addAll(loadedProps); + } + newLoadedProps.add(lazyLoadProperty); + ebi.setLoadedProps(newLoadedProps); + ebi.setLoadedLazy(); + return true; + } + + return false; + } + + /** + * Return a Comparator for local sorting of lists. + * + * @param sortByClause + * list of property names with optional ASC or DESC suffix. + */ + @SuppressWarnings("unchecked") + private ElComparator createComparator(String sortByClause) { + + SortByClause sortBy = SortByClauseParser.parse(sortByClause); + if (sortBy.size() == 1) { + // simple comparator for a single property + return createPropertyComparator(sortBy.getProperties().get(0)); + } + + // create a compound comparator based on the list of properties + ElComparator[] comparators = new ElComparator[sortBy.size()]; + + List sortProps = sortBy.getProperties(); + for (int i = 0; i < sortProps.size(); i++) { + Property sortProperty = sortProps.get(i); + comparators[i] = createPropertyComparator(sortProperty); + } + + return new ElComparatorCompound(comparators); + } + + private ElComparator createPropertyComparator(Property sortProp) { + + ElPropertyValue elGetValue = getElGetValue(sortProp.getName()); + + Boolean nullsHigh = sortProp.getNullsHigh(); + if (nullsHigh == null) { + nullsHigh = Boolean.TRUE; + } + return new ElComparatorProperty(elGetValue, sortProp.isAscending(), nullsHigh); + } + + /** + * Get an Expression language Value object. + */ + public ElPropertyValue getElGetValue(String propName) { + return getElPropertyValue(propName, false); + } + + /** + * Similar to ElPropertyValue but also uses foreign key shortcuts. + *

+ * The foreign key shortcuts means we can avoid unnecessary joins. + *

+ */ + public ElPropertyDeploy getElPropertyDeploy(String propName) { + ElPropertyDeploy fk = fkeyMap.get(propName); + if (fk != null) { + return fk; + } + return getElPropertyValue(propName, true); + } + + private ElPropertyValue getElPropertyValue(String propName, boolean propertyDeploy) { + ElPropertyValue elGetValue = elGetCache.get(propName); + if (elGetValue == null) { + // need to build it potentially navigating the BeanDescriptors + elGetValue = buildElGetValue(propName, null, propertyDeploy); + if (elGetValue == null) { + return null; + } + if (elGetValue instanceof BeanFkeyProperty) { + fkeyMap.put(propName, (BeanFkeyProperty) elGetValue); + } else { + elGetCache.put(propName, elGetValue); + } + } + return elGetValue; + } + + protected ElPropertyValue buildElGetValue(String propName, ElPropertyChainBuilder chain, boolean propertyDeploy) { + + if (propertyDeploy && chain != null) { + BeanFkeyProperty fk = fkeyMap.get(propName); + if (fk != null) { + return fk.create(chain.getExpression()); + } + } + + int basePos = propName.indexOf('.'); + if (basePos > -1) { + // nested or embedded property + String baseName = propName.substring(0, basePos); + String remainder = propName.substring(basePos + 1); + + BeanProperty assocProp = _findBeanProperty(baseName); + if (assocProp == null) { + return null; + } + return assocProp.buildElPropertyValue(propName, remainder, chain, propertyDeploy); + } + + BeanProperty property = _findBeanProperty(propName); + if (chain == null) { + return property; + } + if (property == null) { + throw new PersistenceException("No property found for [" + propName + "] in expression " + chain.getExpression()); + } + if (property.containsMany()) { + chain.setContainsMany(true); + } + return chain.add(property).build(); + } + + /** + * Find a BeanProperty including searching the inheritance hierarchy. + *

+ * This searches this BeanDescriptor and then searches further down the + * inheritance tree (not up). + *

+ */ + public BeanProperty findBeanProperty(String propName) { + int basePos = propName.indexOf('.'); + if (basePos > -1) { + // embedded property + String baseName = propName.substring(0, basePos); + return _findBeanProperty(baseName); + } + + return _findBeanProperty(propName); + } + + private BeanProperty _findBeanProperty(String propName) { + BeanProperty prop = propMap.get(propName); + if (prop == null && inheritInfo != null) { + // search in sub types... + return inheritInfo.findSubTypeProperty(propName); + } + return prop; + } + + protected Object getBeanPropertyWithInheritance(Object bean, String propName) { + + BeanDescriptor desc = getBeanDescriptor(bean.getClass()); + BeanProperty beanProperty = desc.findBeanProperty(propName); + return beanProperty.getValue(bean); + } + + /** + * Return the name of the server this BeanDescriptor belongs to. + */ + public String getServerName() { + return serverName; + } + + /** + * Return true if this bean can cache sharable instances. + *

+ * This means is has no relationships and has readOnly=true in its cache + * options. + *

+ */ + public boolean isCacheSharableBeans() { + return cacheSharableBeans; + } + + /** + * Return true if queries for beans of this type are autoFetch tunable. + */ + public boolean isAutoFetchTunable() { + return autoFetchTunable; + } + + /** + * Returns the Inheritance mapping information. This will be null if this type + * of bean is not involved in any ORM inheritance mapping. + */ + public InheritInfo getInheritInfo() { + return inheritInfo; + } + + /** + * Return true if this is an embedded bean. + */ + public boolean isEmbedded() { + return EntityType.EMBEDDED.equals(entityType); + } + + public boolean isBaseTableType() { + return EntityType.ORM.equals(entityType); + } + + /** + * Return the concurrency mode used for beans of this type. + */ + public ConcurrencyMode getConcurrencyMode() { + return concurrencyMode; + } + + /** + * Return the tables this bean is dependent on. This implies that if any of + * these tables are modified then cached beans may be invalidated. + */ + public String[] getDependantTables() { + return dependantTables; + } + + /** + * Return the compound unique constraints. + */ + public CompoundUniqueContraint[] getCompoundUniqueConstraints() { + return compoundUniqueConstraints; + } + + /** + * Return the beanListener. + */ + public BeanPersistListener getPersistListener() { + return persistListener; + } + + /** + * Return the beanFinder. Usually null unless overriding the finder. + */ + public BeanFinder getBeanFinder() { + return beanFinder; + } + + /** + * Return the BeanQueryAdapter or null if none is defined. + */ + public BeanQueryAdapter getQueryAdapter() { + return queryAdapter; + } + + /** + * De-register the BeanPersistListener. + */ + @SuppressWarnings("unchecked") + public void deregister(BeanPersistListener listener) { + // volatile read... + BeanPersistListener currListener = persistListener; + if (currListener == null) { + // nothing to deregister + } else { + BeanPersistListener deregListener = (BeanPersistListener) listener; + if (currListener instanceof ChainedBeanPersistListener) { + // remove it from the existing chain + persistListener = ((ChainedBeanPersistListener) currListener).deregister(deregListener); + } else if (currListener.equals(deregListener)) { + persistListener = null; + } + } + } + + /** + * De-register the BeanPersistController. + */ + public void deregister(BeanPersistController controller) { + // volatile read... + BeanPersistController c = persistController; + if (c == null) { + // nothing to deregister + } else { + if (c instanceof ChainedBeanPersistController) { + // remove it from the existing chain + persistController = ((ChainedBeanPersistController) c).deregister(controller); + } else if (c.equals(controller)) { + persistController = null; + } + } + } + + /** + * Register the new BeanPersistController. + */ + @SuppressWarnings("unchecked") + public void register(BeanPersistListener newPersistListener) { + + if (!PersistListenerManager.isRegisterFor(beanType, newPersistListener)) { + // skip + } else { + BeanPersistListener newListener = (BeanPersistListener) newPersistListener; + // volatile read... + BeanPersistListener currListener = persistListener; + if (currListener == null) { + persistListener = newListener; + } else { + if (currListener instanceof ChainedBeanPersistListener) { + // add it to the existing chain + persistListener = ((ChainedBeanPersistListener) currListener).register(newListener); + } else { + // build new chain of the 2 + persistListener = new ChainedBeanPersistListener(currListener, newListener); + } + } + } + } + + /** + * Register the new BeanPersistController. + */ + public void register(BeanPersistController newController) { + + if (!newController.isRegisterFor(beanType)) { + // skip + } else { + // volatile read... + BeanPersistController c = persistController; + if (c == null) { + persistController = newController; + } else { + if (c instanceof ChainedBeanPersistController) { + // add it to the existing chain + persistController = ((ChainedBeanPersistController) c).register(newController); + } else { + // build new chain of the 2 + persistController = new ChainedBeanPersistController(c, newController); + } + } + } + } + + /** + * Return the Controller. + */ + public BeanPersistController getPersistController() { + return persistController; + } + + /** + * Returns true if this bean is based on a table (or possibly view) and + * returns false if this bean is based on a raw sql select statement. + *

+ * When false querying this bean is based on a supplied sql select statement + * placed in the orm xml file (as opposed to Ebean generated sql). + *

+ */ + public boolean isSqlSelectBased() { + return EntityType.SQL.equals(entityType); + } + + /** + * Return true if this an LDAP object. + */ + public boolean isLdapEntityType() { + return EntityType.LDAP.equals(entityType); + } + + /** + * Return the base table. Only properties mapped to the base table are by + * default persisted. + */ + public String getBaseTable() { + return baseTable; + } + + /** + * Get a named extra attribute. + */ + public String getExtraAttribute(String key) { + return (String) extraAttrMap.get(key); + } + + /** + * Return the identity generation type. + */ + public IdType getIdType() { + return idType; + } + + /** + * Return the sequence name. + */ + public String getSequenceName() { + return sequenceName; + } + + /** + * Return the SQL used to return the last inserted id. + *

+ * This is only used with Identity columns and getGeneratedKeys is not + * supported. + *

+ */ + public String getSelectLastInsertedId() { + return selectLastInsertedId; + } + + /** + * Return the IdGenerator. + */ + public IdGenerator getIdGenerator() { + return idGenerator; + } + + /** + * Return the includes for getReference(). + */ + public String getLazyFetchIncludes() { + return lazyFetchIncludes; + } + + /** + * Return the TableJoins. + *

+ * For properties mapped to secondary tables rather than the base table. + *

+ */ + public TableJoin[] tableJoins() { + return derivedTableJoins; + } + + /** + * Return an Iterator of all BeanProperty. This includes transient properties. + */ + public Iterator propertiesAll() { + return propMap.values().iterator(); + } + + /** + * Return the BeanProperty that make up the unique id. + *

+ * The order of these properties can be relied on to be consistent if the bean + * itself doesn't change or the xml deployment order does not change. + *

+ */ + public BeanProperty[] propertiesId() { + return propertiesId; + } + + /** + * Return the non transient non id properties. + */ + public BeanProperty[] propertiesNonTransient() { + return propertiesNonTransient; + } + + /** + * Return the transient properties. + */ + public BeanProperty[] propertiesTransient() { + return propertiesTransient; + } + + /** + * If the Id is a single non-embedded property then returns that, otherwise + * returns null. + */ + public BeanProperty getSingleIdProperty() { + return propertySingleId; + } + + /** + * Return the beans that are embedded. These share the base table with the + * owner bean. + */ + public BeanPropertyAssocOne[] propertiesEmbedded() { + return propertiesEmbedded; + } + + /** + * All the BeanPropertyAssocOne that are not embedded. These are effectively + * joined beans. For ManyToOne and OneToOne associations. + */ + public BeanPropertyAssocOne[] propertiesOne() { + return propertiesOne; + } + + /** + * Returns ManyToOnes and OneToOnes on the imported owning side. + *

+ * Excludes OneToOnes on the exported side. + *

+ */ + public BeanPropertyAssocOne[] propertiesOneImported() { + return propertiesOneImported; + } + + /** + * Imported Assoc Ones with cascade save true. + */ + public BeanPropertyAssocOne[] propertiesOneImportedSave() { + return propertiesOneImportedSave; + } + + /** + * Imported Assoc Ones with cascade delete true. + */ + public BeanPropertyAssocOne[] propertiesOneImportedDelete() { + return propertiesOneImportedDelete; + } + + /** + * Returns OneToOnes that are on the exported side of a OneToOne. + *

+ * These associations do not own the relationship. + *

+ */ + public BeanPropertyAssocOne[] propertiesOneExported() { + return propertiesOneExported; + } + + /** + * Exported assoc ones with cascade save. + */ + public BeanPropertyAssocOne[] propertiesOneExportedSave() { + return propertiesOneExportedSave; + } + + /** + * Exported assoc ones with delete cascade. + */ + public BeanPropertyAssocOne[] propertiesOneExportedDelete() { + return propertiesOneExportedDelete; + } + + private Set deriveManyPropNames() { + + LinkedHashSet names = new LinkedHashSet(); + for (int i = 0; i < propertiesMany.length; i++) { + names.add(propertiesMany[i].getName()); + } + + return Collections.unmodifiableSet(names); + } + + /** + * Return a hash of the names of the many properties on this bean type. This + * is used for efficient building of included properties sets for partial + * objects. + */ + public int getNamesOfManyPropsHash() { + return namesOfManyPropsHash; + } + + /** + * Returns the set of many property names for this bean type. + */ + public Set getNamesOfManyProps() { + return namesOfManyProps; + } + + /** + * All Non Assoc Many's for this descriptor. + */ + public BeanProperty[] propertiesNonMany() { + return propertiesNonMany; + } + + /** + * All Assoc Many's for this descriptor. + */ + public BeanPropertyAssocMany[] propertiesMany() { + return propertiesMany; + } + + /** + * Assoc Many's with save cascade. + */ + public BeanPropertyAssocMany[] propertiesManySave() { + return propertiesManySave; + } + + /** + * Assoc Many's with delete cascade. + */ + public BeanPropertyAssocMany[] propertiesManyDelete() { + return propertiesManyDelete; + } + + /** + * Assoc ManyToMany's. + */ + public BeanPropertyAssocMany[] propertiesManyToMany() { + return propertiesManyToMany; + } + + /** + * Return the first version property that exists on the bean. Returns null if + * no version property exists on the bean. + *

+ * Note that this DOES NOT find a version property on an embedded bean. + *

+ */ + public BeanProperty firstVersionProperty() { + return propertyFirstVersion; + } + + /** + * Return true if this an Insert (rather than Update) on a non-enhanced bean. + */ + public boolean isVanillaInsert(Object bean) { + if (propertyFirstVersion == null) { + return true; + } + Object versionValue = propertyFirstVersion.getValue(bean); + return DmlUtil.isNullOrZero(versionValue); + } + + /** + * Return true if this is an Update (rather than insert) given that the bean + * is involved in a stateless update. + */ + public boolean isStatelessUpdate(Object bean) { + if (propertyFirstVersion == null) { + Object versionValue = getId(bean); + return !DmlUtil.isNullOrZero(versionValue); + } else { + Object versionValue = propertyFirstVersion.getValue(bean); + return !DmlUtil.isNullOrZero(versionValue); + } + } + + /** + * Returns 'Version' properties on this bean. These are 'Counter' or 'Update + * Timestamp' type properties. Note version properties can also be on embedded + * beans rather than on the bean itself. + */ + public BeanProperty[] propertiesVersion() { + return propertiesVersion; + } + + /** + * Scalar properties without the unique id or secondary table properties. + */ + public BeanProperty[] propertiesBaseScalar() { + return propertiesBaseScalar; + } + + /** + * Return properties that are immutable compound value objects. + *

+ * These are compound types but are not enhanced (Embedded are enhanced). + *

+ */ + public BeanPropertyCompound[] propertiesBaseCompound() { + return propertiesBaseCompound; + } + + /** + * Return the properties local to this type for inheritance. + */ + public BeanProperty[] propertiesLocal() { + return propertiesLocal; + } + + public void jsonWrite(WriteJsonContext ctx, Object bean) { + + if (bean != null) { + + ctx.appendObjectBegin(); + WriteBeanState prevState = ctx.pushBeanState(bean); + + if (inheritInfo != null) { + InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass()); + String discValue = localInheritInfo.getDiscriminatorStringValue(); + String discColumn = localInheritInfo.getDiscriminatorColumn(); + ctx.appendDiscriminator(discColumn, discValue); + + BeanDescriptor localDescriptor = localInheritInfo.getBeanDescriptor(); + localDescriptor.jsonWriteProperties(ctx, bean); + + } else { + jsonWriteProperties(ctx, bean); + } + + ctx.pushPreviousState(prevState); + ctx.appendObjectEnd(); + } + } + + @SuppressWarnings("unchecked") + private void jsonWriteProperties(WriteJsonContext ctx, Object bean) { + + boolean referenceBean = ctx.isReferenceBean(); + + JsonWriteBeanVisitor beanVisitor = (JsonWriteBeanVisitor) ctx.getBeanVisitor(); + + Set props = ctx.getIncludeProperties(); + + boolean explicitAllProps; + if (props == null) { + explicitAllProps = false; + } else { + explicitAllProps = props.contains("*"); + if (explicitAllProps || props.isEmpty()) { + props = null; + } + } + + for (int i = 0; i < propertiesId.length; i++) { + Object idValue = propertiesId[i].getValue(bean); + if (idValue != null) { + if (props == null || props.contains(propertiesId[i].getName())) { + propertiesId[i].jsonWrite(ctx, bean); + } + } + } + + if (!explicitAllProps && props == null) { + // just render the loaded properties + props = ctx.getLoadedProps(); + } + if (props != null) { + // render only the appropriate properties (when not all properties) + for (String prop : props) { + BeanProperty p = getBeanProperty(prop); + if (p != null && !p.isId()) { + p.jsonWrite(ctx, bean); + } + } + } else { + if (explicitAllProps || !referenceBean) { + // render all the properties and invoke lazy loading if required + for (int j = 0; j < propertiesNonTransient.length; j++) { + propertiesNonTransient[j].jsonWrite(ctx, bean); + } + } + } + + if (beanVisitor != null) { + beanVisitor.visit((T) bean, ctx); + } + } + + @SuppressWarnings("unchecked") + public T jsonReadBean(ReadJsonContext ctx, String path) { + ReadBeanState beanState = jsonRead(ctx, path); + if (beanState == null) { + return null; + } else { + beanState.setLoadedState(); + return (T) beanState.getBean(); + } + } + + public ReadBeanState jsonRead(ReadJsonContext ctx, String path) { + if (!ctx.readObjectBegin()) { + // the object is null + return null; + } + + if (inheritInfo == null) { + return jsonReadObject(ctx, path); + + } else { + // read the discriminator value to determine the correct sub type + String discColumn = inheritInfo.getRoot().getDiscriminatorColumn(); + + if (!ctx.readKeyNext()) { + String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?"; + throw new TextException(msg); + } + String propName = ctx.getTokenKey(); + + if (!propName.equalsIgnoreCase(discColumn)) { + String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but read [" + propName + "]"; + throw new TextException(msg); + } + + String discValue = ctx.readScalarValue(); + if (!ctx.readValueNext()) { + String msg = "Error reading inheritance discriminator [" + discColumn + "]. Expected more json name values?"; + throw new TextException(msg); + } + + // determine the sub type for this particular json object + InheritInfo localInheritInfo = inheritInfo.readType(discValue); + BeanDescriptor localDescriptor = localInheritInfo.getBeanDescriptor(); + return localDescriptor.jsonReadObject(ctx, path); + } + } + + @SuppressWarnings("unchecked") + private ReadBeanState jsonReadObject(ReadJsonContext ctx, String path) { + + T bean = (T) createEntityBean(); + ctx.pushBean(bean, path, this); + + do { + if (!ctx.readKeyNext()) { + break; + } else { + // we read a property key ... + String propName = ctx.getTokenKey(); + BeanProperty p = getBeanProperty(propName); + if (p != null) { + p.jsonRead(ctx, bean); + ctx.setProperty(propName); + } else { + // unknown property key ... + ctx.readUnmappedJson(propName); + } + + if (!ctx.readValueNext()) { + break; + } + } + } while (true); + + return ctx.popBeanState(); + } + + /** + * Set the loaded properties with additional check to see if the bean is a + * reference. + */ + public void setLoadedProps(EntityBeanIntercept ebi, Set loadedProps) { + if (isLoadedReference(loadedProps)) { + ebi.setReference(); + } else { + ebi.setLoadedProps(loadedProps); + } + } + + /** + * Return true if the loadedProperties is just the Id property and therefore + * this is really a reference. + */ + public boolean isLoadedReference(Set loadedProps) { + + if (loadedProps != null) { + if (loadedProps.size() == propertiesId.length) { + for (int i = 0; i < propertiesId.length; i++) { + if (!loadedProps.contains(propertiesId[i].getName())) { + return false; + } + } + return true; + } + } + + return false; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java new file mode 100644 index 000000000..a514b61e9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -0,0 +1,1566 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.RawSql; +import com.avaje.ebean.RawSqlBuilder; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.config.EncryptKey; +import com.avaje.ebean.config.EncryptKeyManager; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.DbIdentity; +import com.avaje.ebean.config.dbplatform.IdGenerator; +import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.validation.factory.LengthValidatorFactory; +import com.avaje.ebean.validation.factory.NotNullValidatorFactory; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.TransactionEventTable; +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.core.InternalConfiguration; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.XmlConfig; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.deploy.id.IdBinderEmbedded; +import com.avaje.ebeaninternal.server.deploy.id.IdBinderFactory; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanTable; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.parse.DeployBeanInfo; +import com.avaje.ebeaninternal.server.deploy.parse.DeployCreateProperties; +import com.avaje.ebeaninternal.server.deploy.parse.DeployInherit; +import com.avaje.ebeaninternal.server.deploy.parse.DeployUtil; +import com.avaje.ebeaninternal.server.deploy.parse.ReadAnnotations; +import com.avaje.ebeaninternal.server.deploy.parse.TransientProperties; +import com.avaje.ebeaninternal.server.idgen.UuidIdGenerator; +import com.avaje.ebeaninternal.server.lib.util.Dnode; +import com.avaje.ebeaninternal.server.reflect.BeanReflect; +import com.avaje.ebeaninternal.server.reflect.BeanReflectFactory; +import com.avaje.ebeaninternal.server.reflect.BeanReflectGetter; +import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter; +import com.avaje.ebeaninternal.server.reflect.EnhanceBeanReflectFactory; +import com.avaje.ebeaninternal.server.subclass.SubClassManager; +import com.avaje.ebeaninternal.server.subclass.SubClassUtil; +import com.avaje.ebeaninternal.server.type.TypeManager; + +/** + * Creates BeanDescriptors. + */ +public class BeanDescriptorManager implements BeanDescriptorMap { + + private static final Logger logger = Logger.getLogger(BeanDescriptorManager.class.getName()); + + private static final BeanDescComparator beanDescComparator = new BeanDescComparator(); + + private final ReadAnnotations readAnnotations = new ReadAnnotations(); + + private final TransientProperties transientProperties; + + /** + * Helper to derive inheritance information. + */ + private final DeployInherit deplyInherit; + + private final BeanReflectFactory reflectFactory; + + private final DeployUtil deployUtil; + + private final TypeManager typeManager; + + private final PersistControllerManager persistControllerManager; + + private final BeanFinderManager beanFinderManager; + + private final PersistListenerManager persistListenerManager; + + private final BeanQueryAdapterManager beanQueryAdapterManager; + + private final SubClassManager subClassManager; + + private final NamingConvention namingConvention; + + private final DeployCreateProperties createProperties; + + private final DeployOrmXml deployOrmXml; + + private final BeanManagerFactory beanManagerFactory; + + private int enhancedClassCount; + private int subclassClassCount; + private final HashSet subclassedEntities = new HashSet(); + + private final boolean updateChangesOnly; + + private final BootupClasses bootupClasses; + + private final String serverName; + + private Map, DeployBeanInfo> deplyInfoMap = new HashMap, DeployBeanInfo>(); + + private final Map, BeanTable> beanTableMap = new HashMap, BeanTable>(); + + private final Map> descMap = new HashMap>(); + private final Map> idDescMap = new HashMap>(); + + private final Map> beanManagerMap = new HashMap>(); + + private final Map>> tableToDescMap = new HashMap>>(); + + private List> immutableDescriptorList; + + private final Set descriptorUniqueIds = new HashSet(); + + private final DbIdentity dbIdentity; + + private final DataSource dataSource; + + private final DatabasePlatform databasePlatform; + + private final UuidIdGenerator uuidIdGenerator = new UuidIdGenerator(); + + private final ServerCacheManager cacheManager; + + private final BackgroundExecutor backgroundExecutor; + + private final int dbSequenceBatchSize; + + private final EncryptKeyManager encryptKeyManager; + + private final IdBinderFactory idBinderFactory; + + private final XmlConfig xmlConfig; + + private final boolean allowSubclassing; + + /** + * Create for a given database dbConfig. + */ + public BeanDescriptorManager(InternalConfiguration config) { + + this.serverName = InternString.intern(config.getServerConfig().getName()); + this.cacheManager = config.getCacheManager(); + this.xmlConfig = config.getXmlConfig(); + this.dbSequenceBatchSize = config.getServerConfig().getDatabaseSequenceBatchSize(); + this.backgroundExecutor = config.getBackgroundExecutor(); + this.dataSource = config.getServerConfig().getDataSource(); + this.encryptKeyManager = config.getServerConfig().getEncryptKeyManager(); + this.databasePlatform = config.getServerConfig().getDatabasePlatform(); + this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm()); + + this.bootupClasses = config.getBootupClasses(); + this.createProperties = config.getDeployCreateProperties(); + this.subClassManager = config.getSubClassManager(); + this.typeManager = config.getTypeManager(); + this.namingConvention = config.getServerConfig().getNamingConvention(); + this.dbIdentity = config.getDatabasePlatform().getDbIdentity(); + this.deplyInherit = config.getDeployInherit(); + this.deployOrmXml = config.getDeployOrmXml(); + this.deployUtil = config.getDeployUtil(); + + this.beanManagerFactory = new BeanManagerFactory(config.getServerConfig(), config.getDatabasePlatform()); + + this.updateChangesOnly = config.getServerConfig().isUpdateChangesOnly(); + + this.persistControllerManager = new PersistControllerManager(bootupClasses); + this.persistListenerManager = new PersistListenerManager(bootupClasses); + this.beanQueryAdapterManager = new BeanQueryAdapterManager(bootupClasses); + + this.beanFinderManager = new DefaultBeanFinderManager(); + + this.reflectFactory = createReflectionFactory(); + this.transientProperties = new TransientProperties(); + this.allowSubclassing = config.getServerConfig().isAllowSubclassing(); + } + + public BeanDescriptor getBeanDescriptorById(String descriptorId) { + return idDescMap.get(descriptorId); + } + + @SuppressWarnings("unchecked") + public BeanDescriptor getBeanDescriptor(Class entityType) { + + // remove $$EntityBean stuff + String className = SubClassUtil.getSuperClassName(entityType.getName()); + return (BeanDescriptor) descMap.get(className); + } + + @SuppressWarnings("unchecked") + public BeanDescriptor getBeanDescriptor(String entityClassName) { + + // remove $$EntityBean stuff + entityClassName = SubClassUtil.getSuperClassName(entityClassName); + return (BeanDescriptor) descMap.get(entityClassName); + } + + public String getServerName() { + return serverName; + } + + public ServerCacheManager getCacheManager() { + return cacheManager; + } + + public NamingConvention getNamingConvention() { + return namingConvention; + } + + /** + * Set the internal EbeanServer instance to all BeanDescriptors. + */ + public void setEbeanServer(SpiEbeanServer internalEbean) { + for (BeanDescriptor desc : immutableDescriptorList) { + desc.setEbeanServer(internalEbean); + } + } + + public IdBinder createIdBinder(BeanProperty[] uids) { + return idBinderFactory.createIdBinder(uids); + } + + public void deploy() { + + try { + createListeners(); + readEmbeddedDeployment(); + readEntityDeploymentInitial(); + readEntityBeanTable(); + readEntityDeploymentAssociations(); + readInheritedIdGenerators(); + + // creates the BeanDescriptors + readEntityRelationships(); + readRawSqlQueries(); + + List> list = new ArrayList>(descMap.values()); + Collections.sort(list, beanDescComparator); + immutableDescriptorList = Collections.unmodifiableList(list); + + // put into map using the "desriptorId" (alternative to class name) + for (BeanDescriptor d : list) { + idDescMap.put(d.getDescriptorId(), d); + } + + initialiseAll(); + readForeignKeys(); + + readTableToDescriptor(); + + logStatus(); + + deplyInfoMap.clear(); + deplyInfoMap = null; + } catch (RuntimeException e) { + String msg = "Error in deployment"; + logger.log(Level.SEVERE, msg, e); + throw e; + } + } + + /** + * Return the Encrypt key given the table and column name. + */ + public EncryptKey getEncryptKey(String tableName, String columnName) { + return encryptKeyManager.getEncryptKey(tableName, columnName); + } + + /** + * For SQL based modifications we need to invalidate appropriate parts of the + * cache. + */ + public void cacheNotify(TransactionEventTable.TableIUD tableIUD) { + + List> list = getBeanDescriptors(tableIUD.getTableName()); + if (list != null) { + for (int i = 0; i < list.size(); i++) { + list.get(i).cacheNotify(tableIUD); + } + } + } + + /** + * Return the BeanDescriptors mapped to the table. + */ + public List> getBeanDescriptors(String tableName) { + return tableToDescMap.get(tableName.toLowerCase()); + } + + /** + * Build a map of table names to BeanDescriptors. + *

+ * This is generally used to maintain caches from table names. + *

+ */ + private void readTableToDescriptor() { + + for (BeanDescriptor desc : descMap.values()) { + String baseTable = desc.getBaseTable(); + if (baseTable == null) { + + } else { + baseTable = baseTable.toLowerCase(); + + List> list = tableToDescMap.get(baseTable); + if (list == null) { + list = new ArrayList>(1); + tableToDescMap.put(baseTable, list); + } + list.add(desc); + } + } + } + + private void readForeignKeys() { + + for (BeanDescriptor d : descMap.values()) { + d.initialiseFkeys(); + } + } + + /** + * Initialise all the BeanDescriptors. + *

+ * This occurs after all the BeanDescriptors have been created. This resolves + * circular relationships between BeanDescriptors. + *

+ *

+ * Also responsible for creating all the BeanManagers which contain the + * persister, listener etc. + *

+ */ + private void initialiseAll() { + + // now that all the BeanDescriptors are in their map + // we can initialise them which sorts out circular + // dependencies for OneToMany and ManyToOne etc + + // PASS 1: + // initialise the ID properties of all the beans + // first (as they are needed to initialise the + // associated properties in the second pass). + for (BeanDescriptor d : descMap.values()) { + d.initialiseId(); + } + + // PASS 2: + // now initialise all the inherit info + for (BeanDescriptor d : descMap.values()) { + d.initInheritInfo(); + } + + // PASS 3: + // now initialise all the associated properties + for (BeanDescriptor d : descMap.values()) { + d.initialiseOther(); + } + + // create BeanManager for each non-embedded entity bean + for (BeanDescriptor d : descMap.values()) { + if (!d.isEmbedded()) { + BeanManager m = beanManagerFactory.create(d); + beanManagerMap.put(d.getFullName(), m); + + checkForValidEmbeddedId(d); + } + } + } + + private void checkForValidEmbeddedId(BeanDescriptor d) { + IdBinder idBinder = d.getIdBinder(); + if (idBinder != null && idBinder instanceof IdBinderEmbedded) { + IdBinderEmbedded embId = (IdBinderEmbedded) idBinder; + BeanDescriptor idBeanDescriptor = embId.getIdBeanDescriptor(); + Class idType = idBeanDescriptor.getBeanType(); + try { + idType.getDeclaredMethod("hashCode", new Class[] {}); + idType.getDeclaredMethod("equals", new Class[] { Object.class }); + } catch (NoSuchMethodException e) { + checkMissingHashCodeOrEquals(e, idType, d.getBeanType()); + } + } + } + + private void checkMissingHashCodeOrEquals(Exception source, Class idType, Class beanType) { + + String msg = "SERIOUS ERROR: The hashCode() and equals() methods *MUST* be implemented "; + msg += "on Embedded bean " + idType + " as it is used as an Id for " + beanType; + + if (GlobalProperties.getBoolean("ebean.strict", true)) { + throw new PersistenceException(msg, source); + } else { + logger.log(Level.SEVERE, msg, source); + } + } + + /** + * Return an immutable list of all the BeanDescriptors. + */ + public List> getBeanDescriptorList() { + return immutableDescriptorList; + } + + public Map, BeanTable> getBeanTables() { + return beanTableMap; + } + + public BeanTable getBeanTable(Class type) { + return beanTableMap.get(type); + } + + public Map> getBeanDescriptors() { + return descMap; + } + + @SuppressWarnings("unchecked") + public BeanManager getBeanManager(Class entityType) { + + return (BeanManager) getBeanManager(entityType.getName()); + } + + public BeanManager getBeanManager(String beanClassName) { + + beanClassName = SubClassUtil.getSuperClassName(beanClassName); + return beanManagerMap.get(beanClassName); + } + + public DNativeQuery getNativeQuery(String name) { + return deployOrmXml.getNativeQuery(name); + } + + /** + * Create the BeanControllers, BeanFinders and BeanListeners. + */ + private void createListeners() { + + int qa = beanQueryAdapterManager.getRegisterCount(); + int cc = persistControllerManager.getRegisterCount(); + int lc = persistListenerManager.getRegisterCount(); + int fc = beanFinderManager.createBeanFinders(bootupClasses.getBeanFinders()); + + logger + .fine("BeanPersistControllers[" + cc + "] BeanFinders[" + fc + "] BeanPersistListeners[" + lc + "] BeanQueryAdapters[" + qa + "]"); + } + + /** + * Log Warning if mixing subclass and enhancement. + *

+ * If enhancement is used for some classes it is expected to be used for all + * and vice versa. + *

+ */ + private void logStatus() { + + String msg = "Entities enhanced[" + enhancedClassCount + "] subclassed[" + subclassClassCount + "]"; + logger.info(msg); + + if (enhancedClassCount > 0) { + if (subclassClassCount > 0) { + String subclassEntityNames = subclassedEntities.toString(); + + String m = "Mixing enhanced and subclassed entities. Subclassed classes:" + subclassEntityNames; + logger.warning(m); + } + } + } + + private BeanDescriptor createEmbedded(Class beanClass) { + + DeployBeanInfo info = createDeployBeanInfo(beanClass); + readDeployAssociations(info); + + Integer key = getUniqueHash(info.getDescriptor()); + + return new BeanDescriptor(this, typeManager, info.getDescriptor(), key.toString()); + } + + private void registerBeanDescriptor(BeanDescriptor desc) { + descMap.put(desc.getBeanType().getName(), desc); + } + + /** + * Read deployment information for all the embedded beans. + */ + private void readEmbeddedDeployment() { + + ArrayList> embeddedClasses = bootupClasses.getEmbeddables(); + for (int i = 0; i < embeddedClasses.size(); i++) { + Class cls = embeddedClasses.get(i); + if (logger.isLoggable(Level.FINER)) { + String msg = "load deployinfo for embeddable:" + cls.getName(); + logger.finer(msg); + } + BeanDescriptor embDesc = createEmbedded(cls); + registerBeanDescriptor(embDesc); + } + } + + /** + * Read the initial deployment information for the entities. + *

+ * This stops short of reading relationship meta data until after the + * BeanTables have all been created. + *

+ */ + private void readEntityDeploymentInitial() { + + ArrayList> entityClasses = bootupClasses.getEntities(); + + for (Class entityClass : entityClasses) { + DeployBeanInfo info = createDeployBeanInfo(entityClass); + deplyInfoMap.put(entityClass, info); + } + } + + /** + * Create the BeanTable information which has the base table and id. + *

+ * This is determined prior to resolving relationship information. + *

+ */ + private void readEntityBeanTable() { + + Iterator> it = deplyInfoMap.values().iterator(); + while (it.hasNext()) { + DeployBeanInfo info = it.next(); + BeanTable beanTable = createBeanTable(info); + beanTableMap.put(beanTable.getBeanType(), beanTable); + } + } + + /** + * Create the BeanTable information which has the base table and id. + *

+ * This is determined prior to resolving relationship information. + *

+ */ + private void readEntityDeploymentAssociations() { + + Iterator> it = deplyInfoMap.values().iterator(); + while (it.hasNext()) { + DeployBeanInfo info = it.next(); + readDeployAssociations(info); + } + } + + private void readInheritedIdGenerators() { + + Iterator> it = deplyInfoMap.values().iterator(); + while (it.hasNext()) { + DeployBeanInfo info = it.next(); + DeployBeanDescriptor descriptor = info.getDescriptor(); + InheritInfo inheritInfo = descriptor.getInheritInfo(); + if (inheritInfo != null && !inheritInfo.isRoot()) { + DeployBeanInfo rootBeanInfo = deplyInfoMap.get(inheritInfo.getRoot().getType()); + IdGenerator rootIdGen = rootBeanInfo.getDescriptor().getIdGenerator(); + if (rootIdGen != null) { + descriptor.setIdGenerator(rootIdGen); + } + } + } + } + + /** + * Create the BeanTable from the deployment information gathered so far. + */ + private BeanTable createBeanTable(DeployBeanInfo info) { + + DeployBeanDescriptor deployDescriptor = info.getDescriptor(); + DeployBeanTable beanTable = deployDescriptor.createDeployBeanTable(); + return new BeanTable(beanTable, this); + } + + /** + * Parse the named Raw Sql queries using BeanDescriptor. + */ + private void readRawSqlQueries() { + + for (DeployBeanInfo info : deplyInfoMap.values()) { + + DeployBeanDescriptor deployDesc = info.getDescriptor(); + BeanDescriptor desc = getBeanDescriptor(deployDesc.getBeanType()); + + for (DRawSqlMeta rawSqlMeta : deployDesc.getRawSqlMeta()) { + if (rawSqlMeta.getQuery() == null) { + + } else { + DeployNamedQuery nq = new DRawSqlSelectBuilder(namingConvention, desc, rawSqlMeta).parse(); + desc.addNamedQuery(nq); + } + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void readEntityRelationships() { + + // We only perform 'circular' checks etc after we have + // all the DeployBeanDescriptors created and in the map. + + for (DeployBeanInfo info : deplyInfoMap.values()) { + checkMappedBy(info); + } + + for (DeployBeanInfo info : deplyInfoMap.values()) { + secondaryPropsJoins(info); + } + + for (DeployBeanInfo info : deplyInfoMap.values()) { + DeployBeanDescriptor deployBeanDescriptor = info.getDescriptor(); + Integer key = getUniqueHash(deployBeanDescriptor); + registerBeanDescriptor(new BeanDescriptor(this, typeManager, info.getDescriptor(), key.toString())); + } + } + + private Integer getUniqueHash(DeployBeanDescriptor deployBeanDescriptor) { + + int hashCode = deployBeanDescriptor.getFullName().hashCode(); + + for (int i = 0; i < 100000; i++) { + Integer key = Integer.valueOf(hashCode + i); + if (!descriptorUniqueIds.contains(key)) { + return key; + } + } + throw new RuntimeException("Failed to generate a unique hash for " + deployBeanDescriptor.getFullName()); + } + + private void secondaryPropsJoins(DeployBeanInfo info) { + + DeployBeanDescriptor descriptor = info.getDescriptor(); + for (DeployBeanProperty prop : descriptor.propertiesBase()) { + if (prop.isSecondaryTable()) { + String tableName = prop.getSecondaryTable(); + // find a join to that table... + DeployBeanPropertyAssocOne assocOne = descriptor.findJoinToTable(tableName); + if (assocOne == null) { + String msg = "Error with property " + prop.getFullBeanName() + ". Could not find a Relationship to table " + tableName + + ". Perhaps you could use a @JoinColumn instead."; + throw new RuntimeException(msg); + } + DeployTableJoin tableJoin = assocOne.getTableJoin(); + prop.setSecondaryTableJoin(tableJoin, assocOne.getName()); + } + } + } + + /** + * Check the mappedBy attributes for properties on this descriptor. + *

+ * This will read join information defined on the 'owning/other' side of the + * relationship. It also does some extra work for unidirectional + * relationships. + *

+ */ + private void checkMappedBy(DeployBeanInfo info) { + + for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { + if (!oneProp.isTransient()) { + if (oneProp.getMappedBy() != null) { + checkMappedByOneToOne(info, oneProp); + } + } + } + + for (DeployBeanPropertyAssocMany manyProp : info.getDescriptor().propertiesAssocMany()) { + if (!manyProp.isTransient()) { + if (manyProp.isManyToMany()) { + checkMappedByManyToMany(info, manyProp); + } else { + checkMappedByOneToMany(info, manyProp); + } + } + } + } + + private DeployBeanDescriptor getTargetDescriptor(DeployBeanPropertyAssoc prop) { + + Class targetType = prop.getTargetType(); + DeployBeanInfo info = deplyInfoMap.get(targetType); + if (info == null) { + String msg = "Can not find descriptor [" + targetType + "] for " + prop.getFullBeanName(); + throw new PersistenceException(msg); + } + + return info.getDescriptor(); + } + + /** + * Check that the many property has either an implied mappedBy property or + * mark it as unidirectional. + */ + private boolean findMappedBy(DeployBeanPropertyAssocMany prop) { + + // this is the entity bean type - that owns this property + Class owningType = prop.getOwningType(); + + Set matchSet = new HashSet(); + + // get the bean descriptor that holds the mappedBy property + DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); + List> ones = targetDesc.propertiesAssocOne(); + for (DeployBeanPropertyAssocOne possibleMappedBy : ones) { + Class possibleMappedByType = possibleMappedBy.getTargetType(); + if (possibleMappedByType.equals(owningType)) { + prop.setMappedBy(possibleMappedBy.getName()); + matchSet.add(possibleMappedBy.getName()); + } + } + + if (matchSet.size() == 0) { + // this is a unidirectional relationship + // ... that is no matching property on the 'detail' bean + return false; + } + if (matchSet.size() == 1) { + // all right with the world + return true; + } + if (matchSet.size() == 2) { + // try to find a match implicitly using a common naming convention + // e.g. List loggedBugs; ... search for "logged" in matchSet + String name = prop.getName(); + + // get the target type short name + String targetType = prop.getTargetType().getName(); + String shortTypeName = targetType.substring(targetType.lastIndexOf(".") + 1); + + // name includes (probably ends with) the target type short name? + int p = name.indexOf(shortTypeName); + if (p > 1) { + // ok, get the 'interesting' part of the property name + // That is the name without the target type + String searchName = name.substring(0, p).toLowerCase(); + + // search for this in the possible matches + Iterator it = matchSet.iterator(); + while (it.hasNext()) { + String possibleMappedBy = it.next(); + String possibleLower = possibleMappedBy.toLowerCase(); + if (possibleLower.indexOf(searchName) > -1) { + // we have a match.. + prop.setMappedBy(possibleMappedBy); + + String m = "Implicitly found mappedBy for " + targetDesc + "." + prop; + m += " by searching for [" + searchName + "] against " + matchSet; + logger.fine(m); + + return true; + } + } + + } + } + // multiple options so should specify mappedBy property + String msg = "Error on " + prop.getFullBeanName() + " missing mappedBy."; + msg += " There are [" + matchSet.size() + "] possible properties in " + targetDesc; + msg += " that this association could be mapped to. Please specify one using "; + msg += "the mappedBy attribute on @OneToMany."; + throw new PersistenceException(msg); + } + + /** + * A OneToMany with no matching mappedBy property in the target so must be + * unidirectional. + *

+ * This means that inserts MUST cascade for this property. + *

+ *

+ * Create a "Shadow"/Unidirectional property on the target. It is used with + * inserts to set the foreign key value (e.g. inserts the foreign key value + * into the order_id column on the order_lines table). + *

+ */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void makeUnidirectional(DeployBeanInfo info, DeployBeanPropertyAssocMany oneToMany) { + + DeployBeanDescriptor targetDesc = getTargetDescriptor(oneToMany); + + Class owningType = oneToMany.getOwningType(); + + if (!oneToMany.getCascadeInfo().isSave()) { + // The property MUST have persist cascading so that inserts work. + + Class targetType = oneToMany.getTargetType(); + String msg = "Error on " + oneToMany.getFullBeanName() + ". @OneToMany MUST have "; + msg += "Cascade.PERSIST or Cascade.ALL because this is a unidirectional "; + msg += "relationship. That is, there is no property of type " + owningType + " on " + targetType; + + throw new PersistenceException(msg); + } + + // mark this property as unidirectional + oneToMany.setUnidirectional(true); + + // create the 'shadow' unidirectional property + // which is put on the target descriptor + DeployBeanPropertyAssocOne unidirectional = new DeployBeanPropertyAssocOne(targetDesc, owningType); + unidirectional.setUndirectionalShadow(true); + unidirectional.setNullable(false); + unidirectional.setDbRead(true); + unidirectional.setDbInsertable(true); + unidirectional.setDbUpdateable(false); + + targetDesc.setUnidirectional(unidirectional); + + // specify table and table alias... + BeanTable beanTable = getBeanTable(owningType); + unidirectional.setBeanTable(beanTable); + unidirectional.setName(beanTable.getBaseTable()); + + info.setBeanJoinType(unidirectional, true); + + // define the TableJoin + DeployTableJoin oneToManyJoin = oneToMany.getTableJoin(); + if (!oneToManyJoin.hasJoinColumns()) { + throw new RuntimeException("No join columns"); + } + + // inverse of the oneToManyJoin + DeployTableJoin unidirectionalJoin = unidirectional.getTableJoin(); + unidirectionalJoin.setColumns(oneToManyJoin.columns(), true); + + } + + private void checkMappedByOneToOne(DeployBeanInfo info, DeployBeanPropertyAssocOne prop) { + + // check that the mappedBy property is valid and read + // its associated join information if it is available + String mappedBy = prop.getMappedBy(); + + // get the mappedBy property + DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); + DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); + if (mappedProp == null) { + String m = "Error on " + prop.getFullBeanName(); + m += " Can not find mappedBy property [" + targetDesc + "." + mappedBy + "] "; + throw new PersistenceException(m); + } + + if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { + String m = "Error on " + prop.getFullBeanName(); + m += ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"; + throw new PersistenceException(m); + } + + DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; + + if (!mappedAssocOne.isOneToOne()) { + String m = "Error on " + prop.getFullBeanName(); + m += ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"; + throw new PersistenceException(m); + } + + DeployTableJoin tableJoin = prop.getTableJoin(); + if (!tableJoin.hasJoinColumns()) { + // define Join as the inverse of the mappedBy property + DeployTableJoin otherTableJoin = mappedAssocOne.getTableJoin(); + otherTableJoin.copyTo(tableJoin, true, tableJoin.getTable()); + } + } + + /** + * If the property has mappedBy set then do two things. Make sure the mappedBy + * property exists, and secondly read its join information. + *

+ * We can use the join information from the mappedBy property and reverse it + * for using in the OneToMany direction. + *

+ */ + private void checkMappedByOneToMany(DeployBeanInfo info, DeployBeanPropertyAssocMany prop) { + + // get the bean descriptor that holds the mappedBy property + + if (prop.getMappedBy() == null) { + if (!findMappedBy(prop)) { + makeUnidirectional(info, prop); + return; + } + } + + // check that the mappedBy property is valid and read + // its associated join information if it is available + String mappedBy = prop.getMappedBy(); + + // get the mappedBy property + DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); + DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); + if (mappedProp == null) { + + String m = "Error on " + prop.getFullBeanName(); + m += " Can not find mappedBy property [" + mappedBy + "] "; + m += "in [" + targetDesc + "]"; + throw new PersistenceException(m); + } + + if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { + String m = "Error on " + prop.getFullBeanName(); + m += ". mappedBy property [" + mappedBy + "]is not a ManyToOne?"; + m += "in [" + targetDesc + "]"; + throw new PersistenceException(m); + } + + DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; + + DeployTableJoin tableJoin = prop.getTableJoin(); + if (!tableJoin.hasJoinColumns()) { + // define Join as the inverse of the mappedBy property + DeployTableJoin otherTableJoin = mappedAssocOne.getTableJoin(); + otherTableJoin.copyTo(tableJoin, true, tableJoin.getTable()); + } + + } + + /** + * For mappedBy copy the joins from the other side. + */ + private void checkMappedByManyToMany(DeployBeanInfo info, DeployBeanPropertyAssocMany prop) { + + // get the bean descriptor that holds the mappedBy property + String mappedBy = prop.getMappedBy(); + if (mappedBy == null) { + return; + } + + // get the mappedBy property + DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); + DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); + + if (mappedProp == null) { + String m = "Error on " + prop.getFullBeanName(); + m += " Can not find mappedBy property [" + mappedBy + "] "; + m += "in [" + targetDesc + "]"; + throw new PersistenceException(m); + } + + if (!(mappedProp instanceof DeployBeanPropertyAssocMany)) { + String m = "Error on " + prop.getFullBeanName(); + m += ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"; + throw new PersistenceException(m); + } + + DeployBeanPropertyAssocMany mappedAssocMany = (DeployBeanPropertyAssocMany) mappedProp; + + if (!mappedAssocMany.isManyToMany()) { + String m = "Error on " + prop.getFullBeanName(); + m += ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"; + throw new PersistenceException(m); + } + + // define the relationships/joins on this side as the + // reverse of the other mappedBy side ... + + // DeployTableJoin mappedJoin = mappedAssocMany.getTableJoin(); + DeployTableJoin mappedIntJoin = mappedAssocMany.getIntersectionJoin(); + DeployTableJoin mappendInverseJoin = mappedAssocMany.getInverseJoin(); + + String intTableName = mappedIntJoin.getTable(); + + DeployTableJoin tableJoin = prop.getTableJoin(); + mappedIntJoin.copyTo(tableJoin, true, targetDesc.getBaseTable()); + + DeployTableJoin intJoin = new DeployTableJoin(); + mappendInverseJoin.copyTo(intJoin, false, intTableName); + prop.setIntersectionJoin(intJoin); + + DeployTableJoin inverseJoin = new DeployTableJoin(); + mappedIntJoin.copyTo(inverseJoin, false, intTableName); + prop.setInverseJoin(inverseJoin); + } + + private void setBeanControllerFinderListener(DeployBeanDescriptor descriptor) { + + Class beanType = descriptor.getBeanType(); + + persistControllerManager.addPersistControllers(descriptor); + persistListenerManager.addPersistListeners(descriptor); + beanQueryAdapterManager.addQueryAdapter(descriptor); + + BeanFinder beanFinder = beanFinderManager.getBeanFinder(beanType); + if (beanFinder != null) { + descriptor.setBeanFinder(beanFinder); + logger.fine("BeanFinder on[" + descriptor.getFullName() + "] " + beanFinder.getClass().getName()); + } + } + + /** + * Read the initial deployment information for a given bean type. + */ + private DeployBeanInfo createDeployBeanInfo(Class beanClass) { + + DeployBeanDescriptor desc = new DeployBeanDescriptor(beanClass); + + desc.setUpdateChangesOnly(updateChangesOnly); + + // set bean controller, finder and listener + setBeanControllerFinderListener(desc); + deplyInherit.process(desc); + + createProperties.createProperties(desc); + + DeployBeanInfo info = new DeployBeanInfo(deployUtil, desc); + + readAnnotations.readInitial(info); + return info; + } + + private void readDeployAssociations(DeployBeanInfo info) { + + DeployBeanDescriptor desc = info.getDescriptor(); + + readAnnotations.readAssociations(info, this); + + readXml(desc); + + if (!EntityType.ORM.equals(desc.getEntityType())) { + // not using base table + desc.setBaseTable(null); + } + + // mark transient properties + transientProperties.process(desc); + setScalarType(desc); + + if (!desc.isEmbedded()) { + // Set IdGenerator or use DB Identity + setIdGeneration(desc); + + // find the appropriate default concurrency mode + setConcurrencyMode(desc); + } + + autoAddValidators(desc); + + // generate the byte code + createByteCode(desc); + } + + /** + * Set the Identity generation mechanism. + */ + private IdType setIdGeneration(DeployBeanDescriptor desc) { + + if (desc.propertiesId().size() == 0) { + // bean doen't have an Id property + if (!desc.isBaseTableType() || desc.getBeanFinder() != null) { + // using BeanFinder so perhaps valid without an id + } else { + // expecting an id property + logger.warning(Message.msg("deploy.nouid", desc.getFullName())); + } + return null; + } + + if (IdType.SEQUENCE.equals(desc.getIdType()) && !dbIdentity.isSupportsSequence()) { + // explicit sequence but not supported by the DatabasePlatform + logger.info("Explicit sequence on " + desc.getFullName() + " but not supported by DB Platform - ignored"); + desc.setIdType(null); + } + if (IdType.IDENTITY.equals(desc.getIdType()) && !dbIdentity.isSupportsIdentity()) { + // explicit identity but not supported by the DatabasePlatform + logger.info("Explicit Identity on " + desc.getFullName() + " but not supported by DB Platform - ignored"); + desc.setIdType(null); + } + + if (desc.getIdType() == null) { + // use the default. IDENTITY or SEQUENCE. + desc.setIdType(dbIdentity.getIdType()); + } + + if (IdType.GENERATOR.equals(desc.getIdType())) { + String genName = desc.getIdGeneratorName(); + if (UuidIdGenerator.AUTO_UUID.equals(genName)) { + desc.setIdGenerator(uuidIdGenerator); + return IdType.GENERATOR; + } + } + + if (desc.getBaseTable() == null) { + // no base table so not going to set Identity + // of sequence information + return null; + } + + if (IdType.IDENTITY.equals(desc.getIdType())) { + // used when getGeneratedKeys is not supported (SQL Server 2000) + String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable()); + desc.setSelectLastInsertedId(selectLastInsertedId); + return IdType.IDENTITY; + } + + String seqName = desc.getIdGeneratorName(); + if (seqName != null) { + logger.fine("explicit sequence " + seqName + " on " + desc.getFullName()); + } else { + String primaryKeyColumn = desc.getSinglePrimaryKeyColumn(); + // use namingConvention to define sequence name + seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn); + } + + // create the sequence based IdGenerator + IdGenerator seqIdGen = createSequenceIdGenerator(seqName); + desc.setIdGenerator(seqIdGen); + + return IdType.SEQUENCE; + } + + private IdGenerator createSequenceIdGenerator(String seqName) { + return databasePlatform.createSequenceIdGenerator(backgroundExecutor, dataSource, seqName, dbSequenceBatchSize); + } + + private void createByteCode(DeployBeanDescriptor deploy) { + + // check to see if the bean supports EntityBean interface + // generate a subclass if required + setEntityBeanClass(deploy); + + // use Code generation or Standard reflection to support + // getter and setter methods + setBeanReflect(deploy); + } + + /** + * Add Length and NotNull validators based on Column annotation etc. + */ + private void autoAddValidators(DeployBeanDescriptor deployDesc) { + + for (DeployBeanProperty prop : deployDesc.propertiesBase()) { + autoAddValidators(prop); + } + } + + /** + * Add Length and NotNull validators based on Column annotation etc. + */ + private void autoAddValidators(DeployBeanProperty prop) { + + if (String.class.equals(prop.getPropertyType()) && prop.getDbLength() > 0) { + // check if the property already has the LengthValidator + if (!prop.containsValidatorType(LengthValidatorFactory.LengthValidator.class)) { + prop.addValidator(LengthValidatorFactory.create(0, prop.getDbLength())); + } + } + if (!prop.isNullable() && !prop.isId() && !prop.isGenerated()) { + // check if the property already has the NotNullValidator + if (!prop.containsValidatorType(NotNullValidatorFactory.NotNullValidator.class)) { + prop.addValidator(NotNullValidatorFactory.NOT_NULL); + } + } + } + + /** + * Set the Scalar Types on all the simple types. This is done AFTER transients + * have been identified. This is because a non-transient field MUST have a + * ScalarType. It is useful for transients to have ScalarTypes because then + * they can be used in a SqlSelect query. + *

+ * Enums are treated a bit differently in that they always have a ScalarType + * as one is built for them. + *

+ */ + private void setScalarType(DeployBeanDescriptor deployDesc) { + + Iterator it = deployDesc.propertiesAll(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop instanceof DeployBeanPropertyAssoc) { + + } else { + deployUtil.setScalarType(prop); + } + } + } + + private void readXml(DeployBeanDescriptor deployDesc) { + + List eXml = xmlConfig.findEntityXml(deployDesc.getFullName()); + readXmlRawSql(deployDesc, eXml); + + Dnode entityXml = deployOrmXml.findEntityDeploymentXml(deployDesc.getFullName()); + + if (entityXml != null) { + readXmlNamedQueries(deployDesc, entityXml); + readXmlSql(deployDesc, entityXml); + } + } + + /** + * Read sql-select (FUTURE: additionally sql-insert, sql-update, sql-delete). + * If found this entity bean is based on raw sql. + */ + private void readXmlSql(DeployBeanDescriptor deployDesc, Dnode entityXml) { + + List sqlSelectList = entityXml.findAll("sql-select", entityXml.getLevel() + 1); + for (int i = 0; i < sqlSelectList.size(); i++) { + Dnode sqlSelect = sqlSelectList.get(i); + readSqlSelect(deployDesc, sqlSelect); + } + } + + private String findContent(Dnode node, String nodeName) { + Dnode found = node.find(nodeName); + if (found != null) { + return found.getNodeContent(); + } else { + return null; + } + } + + private void readSqlSelect(DeployBeanDescriptor deployDesc, Dnode sqlSelect) { + + String name = sqlSelect.getStringAttr("name", "default"); + String extend = sqlSelect.getStringAttr("extend", null); + String queryDebug = sqlSelect.getStringAttr("debug", null); + boolean debug = (queryDebug != null && queryDebug.equalsIgnoreCase("true")); + + // the raw sql select + String query = findContent(sqlSelect, "query"); + String where = findContent(sqlSelect, "where"); + String having = findContent(sqlSelect, "having"); + String columnMapping = findContent(sqlSelect, "columnMapping"); + + DRawSqlMeta m = new DRawSqlMeta(name, extend, query, debug, where, having, columnMapping); + + deployDesc.add(m); + + } + + private void readXmlRawSql(DeployBeanDescriptor deployDesc, List entityXml) { + + List rawSqlQueries = xmlConfig.find(entityXml, "raw-sql"); + for (int i = 0; i < rawSqlQueries.size(); i++) { + Dnode rawSqlDnode = rawSqlQueries.get(i); + String name = rawSqlDnode.getAttribute("name"); + if (isEmpty(name)) { + throw new IllegalStateException("raw-sql for " + deployDesc.getFullName() + " missing name attribute"); + } + Dnode queryNode = rawSqlDnode.find("query"); + if (queryNode == null) { + throw new IllegalStateException("raw-sql for " + deployDesc.getFullName() + " missing query element"); + } + String sql = queryNode.getNodeContent(); + if (isEmpty(sql)) { + throw new IllegalStateException("raw-sql for " + deployDesc.getFullName() + " has empty sql in the query element?"); + } + + List columnMappings = rawSqlDnode.findAll("columnMapping", 1); + + RawSqlBuilder rawSqlBuilder = RawSqlBuilder.parse(sql); + for (int j = 0; j < columnMappings.size(); j++) { + Dnode cm = columnMappings.get(j); + String column = cm.getAttribute("column"); + String property = cm.getAttribute("property"); + rawSqlBuilder.columnMapping(column, property); + } + RawSql rawSql = rawSqlBuilder.create(); + + DeployNamedQuery namedQuery = new DeployNamedQuery(name, rawSql); + deployDesc.add(namedQuery); + } + } + + private boolean isEmpty(String s) { + return s == null || s.trim().length() == 0; + } + + /** + * Read named queries for this bean type. + */ + private void readXmlNamedQueries(DeployBeanDescriptor deployDesc, Dnode entityXml) { + + // look for named-query... + List namedQueries = entityXml.findAll("named-query", 1); + + for (Dnode namedQueryXml : namedQueries) { + + String name = (String) namedQueryXml.getAttribute("name"); + Dnode query = namedQueryXml.find("query"); + if (query == null) { + logger.warning("orm.xml " + deployDesc.getFullName() + " named-query missing query element?"); + + } else { + String oql = query.getNodeContent(); + // TODO: QueryHints not read from xml yet + if (name == null || oql == null) { + logger.warning("orm.xml " + deployDesc.getFullName() + " named-query has no query content?"); + } else { + // add the named query + DeployNamedQuery q = new DeployNamedQuery(name, oql, null); + deployDesc.add(q); + } + } + } + } + + private BeanReflectFactory createReflectionFactory() { + + return new EnhanceBeanReflectFactory(); + } + + /** + * Set BeanReflect BeanReflectGetter and BeanReflectSetter properties. + *

+ * This sets the implementation of constructing entity beans and the setting + * and getting of properties. It is generally faster to use code generation + * rather than reflection to do this. + *

+ */ + private void setBeanReflect(DeployBeanDescriptor desc) { + + // Set the BeanReflectGetter and BeanReflectSetter that typically + // use generated code. NB: Due to Bug 166 so now doing this for + // abstract classes as well. + + Class beanType = desc.getBeanType(); + Class factType = desc.getFactoryType(); + + BeanReflect beanReflect = reflectFactory.create(beanType, factType); + desc.setBeanReflect(beanReflect); + + try { + Iterator it = desc.propertiesAll(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + String propName = prop.getName(); + + if (desc.isAbstract() || beanReflect.isVanillaOnly()) { + // use reflection in the case of imported abstract class + // with + // inheritance. Refer Bug 166 + prop.setGetter(ReflectGetter.create(prop)); + prop.setSetter(ReflectSetter.create(prop)); + + } else { + // use generated code for getting setting property values + BeanReflectGetter getter = beanReflect.getGetter(propName); + BeanReflectSetter setter = beanReflect.getSetter(propName); + prop.setGetter(getter); + prop.setSetter(setter); + if (getter == null) { + // should never happen + String m = "BeanReflectGetter for " + prop.getFullBeanName() + " was not found?"; + throw new RuntimeException(m); + } + } + + } + } catch (IllegalArgumentException e) { + Class superClass = desc.getBeanType().getSuperclass(); + String msg = "Error with [" + desc.getFullName() + "] I believe it is not enhanced but it's superClass [" + superClass + "] is?" + + " (You are not allowed to mix enhancement in a single inheritance hierarchy)"; + throw new PersistenceException(msg, e); + } + } + + /** + * DevNote: It is assumed that Embedded can contain version properties. It is + * also assumed that Embedded beans do NOT themselves contain Embedded beans + * which contain version properties. + */ + private void setConcurrencyMode(DeployBeanDescriptor desc) { + + if (!desc.getConcurrencyMode().equals(ConcurrencyMode.ALL)) { + // concurrency mode explicitly set during deployment + return; + } + + if (checkForVersionProperties(desc)) { + desc.setConcurrencyMode(ConcurrencyMode.VERSION); + } + } + + /** + * Search for version properties also including embedded beans. + */ + private boolean checkForVersionProperties(DeployBeanDescriptor desc) { + + boolean hasVersionProperty = false; + + List props = desc.propertiesBase(); + for (int i = 0; i < props.size(); i++) { + if (props.get(i).isVersionColumn()) { + hasVersionProperty = true; + } + } + + return hasVersionProperty; + } + + private boolean hasEntityBeanInterface(Class beanClass) { + + Class[] interfaces = beanClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + if (interfaces[i].equals(EntityBean.class)) { + return true; + } + } + return false; + } + + /** + * Test the bean type to see if it implements EntityBean interface already. + */ + private void setEntityBeanClass(DeployBeanDescriptor desc) { + + Class beanClass = desc.getBeanType(); + + if (desc.isAbstract()) { + if (hasEntityBeanInterface(beanClass)) { + checkEnhanced(desc, beanClass); + } else { + checkSubclass(desc, beanClass); + } + return; + } + try { + Object testBean = null; + try { + testBean = beanClass.newInstance(); + } catch (InstantiationException e) { + // expected when no default constructor + logger.fine("no default constructor on " + beanClass + " e:" + e); + } catch (IllegalAccessException e) { + // expected when no default constructor + logger.fine("no default constructor on " + beanClass + " e:" + e); + } + if (testBean instanceof EntityBean == false) { + checkSubclass(desc, beanClass); + + } else { + String className = beanClass.getName(); + try { + // check that it really is enhanced (rather than mixed + // enhancement) + String marker = ((EntityBean) testBean)._ebean_getMarker(); + if (!marker.equals(className)) { + String msg = "Error with [" + desc.getFullName() + "] It has not been enhanced but it's superClass [" + + beanClass.getSuperclass() + "] is?" + " (You are not allowed to mix enhancement in a single inheritance hierarchy)" + + " marker[" + marker + "] className[" + className + "]"; + throw new PersistenceException(msg); + } + } catch (AbstractMethodError e) { + throw new PersistenceException("Old Ebean v1.0 enhancement detected in Ebean v1.1 - please do a clean enhancement.", e); + } + + checkEnhanced(desc, beanClass); + } + + } catch (PersistenceException ex) { + throw ex; + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + private void checkEnhanced(DeployBeanDescriptor desc, Class beanClass) { + // the bean already implements EntityBean + checkInheritedClasses(true, beanClass); + + desc.setFactoryType(beanClass); + if (!beanClass.getName().startsWith("com.avaje.ebean.meta")) { + enhancedClassCount++; + } + } + + private void checkSubclass(DeployBeanDescriptor desc, Class beanClass) { + + checkInheritedClasses(false, beanClass); + desc.checkReadAndWriteMethods(); + + EntityType entityType = desc.getEntityType(); + if (EntityType.XMLELEMENT.equals(entityType)) { + desc.setFactoryType(beanClass); + + } else { + if (!allowSubclassing) { + throw new PersistenceException("This configuration does not allow entity subclassing [" + beanClass + "]"); + } + subclassClassCount++; + Class subClass = subClassManager.resolve(beanClass.getName()); + desc.setFactoryType(subClass); + subclassedEntities.add(desc.getName()); + } + } + + /** + * Check that the inherited classes are the same as the entity bean (aka all + * enhanced or all dynamically subclassed). + */ + private void checkInheritedClasses(boolean ensureEnhanced, Class beanClass) { + Class superclass = beanClass.getSuperclass(); + if (Object.class.equals(superclass)) { + // we got to the top of the inheritance + return; + } + boolean isClassEnhanced = EntityBean.class.isAssignableFrom(superclass); + + if (ensureEnhanced != isClassEnhanced) { + String msg; + if (ensureEnhanced) { + msg = "Class [" + superclass + "] is not enhanced and [" + beanClass + "] is - (you can not mix!!)"; + } else { + msg = "Class [" + superclass + "] is enhanced and [" + beanClass + "] is not - (you can not mix!!)"; + } + throw new IllegalStateException(msg); + } + + // recursively continue up the inheritance hierarchy + checkInheritedClasses(ensureEnhanced, superclass); + } + + /** + * Comparator to sort the BeanDescriptors by name. + */ + private static final class BeanDescComparator implements Comparator>, Serializable { + + private static final long serialVersionUID = 1L; + + public int compare(BeanDescriptor o1, BeanDescriptor o2) { + + return o1.getName().compareTo(o2.getName()); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java new file mode 100644 index 000000000..867cb1c99 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.cache.ServerCacheManager; +import com.avaje.ebean.config.EncryptKey; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; + +/** + * Provides a method to find a BeanDescriptor. + *

+ * Used during deployment of to resolve relationships between beans. + *

+ */ +public interface BeanDescriptorMap { + + /** + * Return the name of the server/database. + */ + public String getServerName(); + + /** + * Return the Cache Manager. + */ + public ServerCacheManager getCacheManager(); + + /** + * Return the BeanDescriptor for a given class. + */ + public BeanDescriptor getBeanDescriptor(Class entityType); + + /** + * Return the Encrypt key given the table and column name. + */ + public EncryptKey getEncryptKey(String tableName, String columnName); + + public IdBinder createIdBinder(BeanProperty[] uids); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMeta.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMeta.java new file mode 100644 index 000000000..8b5c63ea9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMeta.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +public class BeanEmbeddedMeta { + + + final BeanProperty[] properties; + + public BeanEmbeddedMeta(BeanProperty[] properties) { + this.properties = properties; + } + + /** + * Return the properties with over ridden mapping information. + */ + public BeanProperty[] getProperties() { + return properties; + } + + /** + * Return true if at least one property is a version property. + */ + public boolean isEmbeddedVersion() { + for (int i = 0; i < properties.length; i++) { + if (properties[i].isVersion()){ + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java new file mode 100644 index 000000000..8832b30ce --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Map; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; + +/** + * Creates BeanProperties for Embedded beans that have deployment information + * such as the actual DB column name and table alias. + */ +public class BeanEmbeddedMetaFactory { + + /** + * Create BeanProperties for embedded beans using the deployment specific DB column name and table alias. + */ + public static BeanEmbeddedMeta create(BeanDescriptorMap owner, DeployBeanPropertyAssocOne prop, + BeanDescriptor descriptor) { + + // we can get a BeanDescriptor for an Embedded bean + // and know that it is NOT recursive, as Embedded beans are + // only allow to hold simple scalar types... + BeanDescriptor targetDesc = owner.getBeanDescriptor(prop.getTargetType()); + if (targetDesc == null){ + String msg = "Could not find BeanDescriptor for "+prop.getTargetType() + +". Perhaps the EmbeddedId class is not registered?"; + throw new PersistenceException(msg); + } + + // deployment override information (column names) + Map propColMap = prop.getDeployEmbedded().getPropertyColumnMap(); + + BeanProperty[] sourceProperties = targetDesc.propertiesBaseScalar(); + + BeanProperty[] embeddedProperties = new BeanProperty[sourceProperties.length]; + + for (int i = 0; i < sourceProperties.length; i++) { + + String propertyName = sourceProperties[i].getName(); + String dbColumn = propColMap.get(propertyName); + if (dbColumn == null) { + // dbColumn not overridden so take original + dbColumn = sourceProperties[i].getDbColumn(); + } + + BeanPropertyOverride overrides = new BeanPropertyOverride(dbColumn); + embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides); + } + + return new BeanEmbeddedMeta(embeddedProperties); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFinderManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFinderManager.java new file mode 100644 index 000000000..539dee383 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFinderManager.java @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.List; + +import com.avaje.ebean.event.BeanFinder; + +/** + * Factory for controlling the construction of BeanFinders. + */ +public interface BeanFinderManager { + + /** + * Return the number of beans with a registered finder. + */ + public int getRegisterCount(); + + /** + * Create the appropriate BeanController. + */ + public int createBeanFinders(List> finderClassList); + + /** + * Return the BeanController for a given entity type. + */ + public BeanFinder getBeanFinder(Class entityType); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java new file mode 100644 index 000000000..1c5742a46 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java @@ -0,0 +1,185 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.text.StringFormatter; +import com.avaje.ebean.text.StringParser; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Used to evaluate imported foreign keys so as to avoid unnecessary joins. + */ +public final class BeanFkeyProperty implements ElPropertyValue { + + private final String placeHolder; + private final String prefix; + private final String name; + private final String dbColumn; + + private int deployOrder; + + public BeanFkeyProperty(String prefix, String name, String dbColumn, int deployOrder) { + this.prefix = prefix; + this.name = name; + this.dbColumn = dbColumn; + this.deployOrder = deployOrder; + this.placeHolder = calcPlaceHolder(prefix, dbColumn); + } + + public int getDeployOrder() { + return deployOrder; + } + + private String calcPlaceHolder(String prefix, String dbColumn) { + if (prefix != null) { + return "${" + prefix + "}" + dbColumn; + } else { + return ROOT_ELPREFIX + dbColumn; + } + } + + public BeanFkeyProperty create(String expression) { + int len = expression.length() - name.length() - 1; + String prefix = expression.substring(0, len); + + return new BeanFkeyProperty(prefix, name, dbColumn, deployOrder); + } + + /** + * Returns false for keys. + */ + public boolean isDbEncrypted() { + return false; + } + + /** + * Returns false for keys. + */ + public boolean isLocalEncrypted() { + return false; + } + + /** + * Only usable as ElPropertyDeploy. + */ + public boolean isDeployOnly() { + return true; + } + + /** + * Returns false. + */ + public boolean containsMany() { + return false; + } + + public boolean containsManySince(String sinceProperty) { + return containsMany(); + } + + public String getDbColumn() { + return dbColumn; + } + + public String getName() { + return name; + } + + public String getElName() { + return name; + } + + /** + * Returns null as not an AssocOne. + */ + public Object[] getAssocOneIdValues(Object value) { + return null; + } + + /** + * Returns null as not an AssocOne. + */ + public String getAssocOneIdExpr(String prefix, String operator) { + return null; + } + + /** + * Returns null as not an AssocOne. + */ + public String getAssocIdInExpr(String prefix) { + return null; + } + + /** + * Returns null as not an AssocOne. + */ + public String getAssocIdInValueExpr(int size) { + return null; + } + + /** + * Returns false as not an AssocOne. + */ + public boolean isAssocId() { + return false; + } + + public boolean isAssocProperty() { + return false; + } + + public String getElPlaceholder(boolean encrypted) { + return placeHolder; + } + + public String getElPrefix() { + return prefix; + } + + public boolean isDateTimeCapable() { + return false; + } + + public int getJdbcType() { + return 0; + } + + public Object parseDateTime(long systemTimeMillis) { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public StringFormatter getStringFormatter() { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public StringParser getStringParser() { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public void elSetReference(Object bean) { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public Object elConvertType(Object value) { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public void elSetValue(Object bean, Object value, boolean populate, boolean reference) { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public Object elGetValue(Object bean) { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public Object elGetReference(Object bean) { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public BeanProperty getBeanProperty() { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + + public String getDeployProperty() { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanForeignKey.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanForeignKey.java new file mode 100644 index 000000000..89e9367ef --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanForeignKey.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.core.InternString; + +/** + * Represents a database foreign key which can map to an object relationship. + */ +public class BeanForeignKey { + + private final String dbColumn; + + private final int dbType; + + /** + * Construct the BeanForeignKey. + */ + public BeanForeignKey(String dbColumn, int dbType) { + this.dbColumn = InternString.intern(dbColumn); + this.dbType = dbType; + } + + /** + * Return the database column. + */ + public String getDbColumn() { + return dbColumn; + } + + /** + * Return the JDBC datatype of the database column. + */ + public int getDbType() { + return dbType; + } + + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof BeanForeignKey) { + return obj.hashCode() == hashCode(); + } + return false; + } + + public int hashCode() { + int hc = getClass().hashCode(); + hc = hc * 31 + (dbColumn != null ? dbColumn.hashCode() : 0); + return hc; + } + + public String toString() { + return dbColumn; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java new file mode 100644 index 000000000..9fdecc077 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java @@ -0,0 +1,167 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.common.BeanList; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; + +/** + * Helper object for dealing with Lists. + */ +public final class BeanListHelp implements BeanCollectionHelp { + + private final BeanPropertyAssocMany many; + private final BeanDescriptor targetDescriptor; + private BeanCollectionLoader loader; + + public BeanListHelp(BeanPropertyAssocMany many) { + this.many = many; + this.targetDescriptor = many.getTargetDescriptor(); + } + + public BeanListHelp() { + this.many = null; + this.targetDescriptor = null; + } + + public void setLoader(BeanCollectionLoader loader) { + this.loader = loader; + } + + public void add(BeanCollection collection, Object bean) { + collection.internalAdd(bean); + } + + public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { + + if (bc instanceof BeanList) { + + BeanList bl = (BeanList) bc; + if (bl.getActualList() == null) { + bl.setActualList(new ArrayList()); + } + return bl; + } else if (bc instanceof List) { + return new VanillaAdd((List) bc); + + } else { + throw new RuntimeException("Unhandled type " + bc); + } + } + + @SuppressWarnings("unchecked") + static class VanillaAdd implements BeanCollectionAdd { + + @SuppressWarnings("rawtypes") + private final List list; + + private VanillaAdd(List list) { + this.list = list; + } + + public void addBean(Object bean) { + list.add(bean); + } + } + + public Iterator getIterator(Object collection) { + return ((List) collection).iterator(); + } + + public Object createEmpty(boolean vanilla) { + return vanilla ? new ArrayList() : new BeanList(); + } + + public BeanCollection createReference(Object parentBean, String propertyName) { + + return new BeanList(loader, parentBean, propertyName); + } + + public ArrayList validate(Object manyValue) { + + ArrayList errs = null; + + List l = (List) manyValue; + for (int i = 0; i < l.size(); i++) { + Object detailBean = l.get(i); + InvalidValue invalid = targetDescriptor.validate(true, detailBean); + if (invalid != null) { + if (errs == null) { + errs = new ArrayList(); + } + errs.add(invalid); + } + } + return errs; + } + + public void refresh(EbeanServer server, Query query, Transaction t, Object parentBean) { + + BeanList newBeanList = (BeanList) server.findList(query, t); + refresh(newBeanList, parentBean); + } + + public void refresh(BeanCollection bc, Object parentBean) { + + BeanList newBeanList = (BeanList) bc; + + List currentList = (List) many.getValueUnderlying(parentBean); + + newBeanList.setModifyListening(many.getModifyListenMode()); + + if (currentList == null) { + // the currentList is null? Not really expecting this... + many.setValue(parentBean, newBeanList); + + } else if (currentList instanceof BeanList) { + // normally this case, replace just the underlying list + BeanList currentBeanList = (BeanList) currentList; + currentBeanList.setActualList(newBeanList.getActualList()); + currentBeanList.setModifyListening(many.getModifyListenMode()); + + } else { + // replace the entire list with the BeanList + many.setValue(parentBean, newBeanList); + } + } + + public void jsonWrite(WriteJsonContext ctx, String name, Object collection, boolean explicitInclude) { + + List list; + if (collection instanceof BeanCollection) { + BeanList beanList = (BeanList) collection; + if (!beanList.isPopulated()) { + if (explicitInclude) { + // invoke lazy loading as collection + // is explicitly included in the output + beanList.size(); + } else { + return; + } + } + list = beanList.getActualList(); + } else { + list = (List) collection; + } + + ctx.beginAssocMany(name); + for (int j = 0; j < list.size(); j++) { + if (j > 0) { + ctx.appendComma(); + } + Object detailBean = list.get(j); + targetDescriptor.jsonWrite(ctx, detailBean); + } + ctx.endAssocMany(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManager.java new file mode 100644 index 000000000..354db2b0d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManager.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.persist.BeanPersister; + +/** + * Holds the BeanDescriptor and its associated BeanPersister. + */ +public class BeanManager { + + private final BeanPersister persister; + + private final BeanDescriptor descriptor; + + public BeanManager(BeanDescriptor descriptor, BeanPersister persister) { + this.descriptor = descriptor; + this.persister = persister; + } + + /** + * Return the associated BeanPersister. + */ + public BeanPersister getBeanPersister() { + return persister; + } + + /** + * Return the BeanDescriptor. + */ + public BeanDescriptor getBeanDescriptor() { + return descriptor; + } + + /** + * Return true if this bean type is an LDAP entity type. + */ + public boolean isLdapEntityType() { + return descriptor.isLdapEntityType(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManagerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManagerFactory.java new file mode 100644 index 000000000..6ee31721d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanManagerFactory.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.server.persist.BeanPersister; +import com.avaje.ebeaninternal.server.persist.BeanPersisterFactory; +import com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersisterFactory; + +/** + * Creates BeanManagers. + */ +public class BeanManagerFactory { + + final BeanPersisterFactory peristerFactory; + + public BeanManagerFactory(ServerConfig config, DatabasePlatform dbPlatform) { + peristerFactory = new DmlBeanPersisterFactory(dbPlatform); + } + + public BeanManager create(BeanDescriptor desc) { + + BeanPersister persister = peristerFactory.create(desc); + + return new BeanManager(desc, persister); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java new file mode 100644 index 000000000..41f97fec6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java @@ -0,0 +1,205 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.common.BeanMap; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; + +/** + * Helper specifically for dealing with Maps. + */ +public final class BeanMapHelp implements BeanCollectionHelp { + + private final BeanPropertyAssocMany many; + private final BeanDescriptor targetDescriptor; + private final BeanProperty beanProperty; + private BeanCollectionLoader loader; + //private final String mapKey; + + /** + * When created for a given query that will return a map. + */ + public BeanMapHelp(BeanDescriptor targetDescriptor, String mapKey) { + this(null, targetDescriptor, mapKey); + } + + public BeanMapHelp(BeanPropertyAssocMany many){ + this(many, many.getTargetDescriptor(), many.getMapKey()); + } + + /** + * When help is attached to a specific many property. + */ + private BeanMapHelp(BeanPropertyAssocMany many, BeanDescriptor targetDescriptor, String mapKey){ + this.many = many; + this.targetDescriptor = targetDescriptor; + //this.mapKey = mapKey; + this.beanProperty = targetDescriptor.getBeanProperty(mapKey); + } + + /** + * Return an iterator of the values. + */ + public Iterator getIterator(Object collection) { + return ((Map) collection).values().iterator(); + } + + public void setLoader(BeanCollectionLoader loader){ + this.loader = loader; + } + + @SuppressWarnings("unchecked") + public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { + + if(mapKey == null){ + mapKey = many.getMapKey(); + } + BeanProperty beanProp = targetDescriptor.getBeanProperty(mapKey); + + if (bc instanceof BeanMap){ + BeanMap bm = (BeanMap)bc; + Map actualMap = bm.getActualMap(); + if (actualMap == null){ + actualMap = new LinkedHashMap(); + bm.setActualMap(actualMap); + } + return new Adder(beanProp, actualMap); + + } else if (bc instanceof Map) { + return new Adder(beanProp, (Map)bc); + + } else { + throw new RuntimeException("Unhandled type "+bc); + } + } + + static class Adder implements BeanCollectionAdd { + + private final BeanProperty beanProperty; + + private final Map map; + + Adder(BeanProperty beanProperty, Map map) { + this.beanProperty = beanProperty; + this.map = map; + } + + public void addBean(Object bean) { + Object keyValue = beanProperty.getValue(bean); + map.put(keyValue, bean); + } + } + + @SuppressWarnings("rawtypes") + public Object createEmpty(boolean vanilla) { + return vanilla ? new LinkedHashMap() : new BeanMap(); + } + + @SuppressWarnings("unchecked") + public void add(BeanCollection collection, Object bean) { + + Object keyValue = beanProperty.getValueIntercept(bean); + + Map map = (Map) collection; + map.put(keyValue, bean); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public BeanCollection createReference(Object parentBean, String propertyName) { + + return new BeanMap(loader, parentBean, propertyName); + } + + public ArrayList validate(Object manyValue) { + + ArrayList errs = null; + + Map m = (Map) manyValue; + Iterator it = m.values().iterator(); + while (it.hasNext()) { + Object detailBean = (Object) it.next(); + InvalidValue invalid = targetDescriptor.validate(true, detailBean); + if (invalid != null) { + if (errs == null) { + errs = new ArrayList(); + } + errs.add(invalid); + } + } + + return errs; + } + + public void refresh(EbeanServer server, Query query, Transaction t, Object parentBean) { + BeanMap newBeanMap = (BeanMap) server.findMap(query, t); + refresh(newBeanMap, parentBean); + } + + public void refresh(BeanCollection bc, Object parentBean) { + + BeanMap newBeanMap = (BeanMap) bc; + Map current = (Map) many.getValueUnderlying(parentBean); + + newBeanMap.setModifyListening(many.getModifyListenMode()); + if (current == null) { + // the currentMap is null? Not really expecting this... + many.setValue(parentBean, newBeanMap); + + } else if (current instanceof BeanMap) { + // normally this case, replace just the underlying list + BeanMap currentBeanMap = (BeanMap) current; + currentBeanMap.setActualMap(newBeanMap.getActualMap()); + currentBeanMap.setModifyListening(many.getModifyListenMode()); + + } else { + // replace the entire set + many.setValue(parentBean, newBeanMap); + } + } + + public void jsonWrite(WriteJsonContext ctx, String name, Object collection, boolean explicitInclude) { + + Map map; + if (collection instanceof BeanCollection){ + BeanMap bc = (BeanMap)collection; + if (!bc.isPopulated()){ + if (explicitInclude){ + // invoke lazy loading as collection + // is explicitly included in the output + bc.size(); + } else { + return; + } + } + map = bc.getActualMap(); + } else { + map = (Map)collection; + } + + int count = 0; + ctx.beginAssocMany(name); + Iterator it = map.entrySet().iterator(); + while (it.hasNext()) { + Entry entry = (Entry)it.next(); + if (count++ > 0){ + ctx.appendComma(); + } + //FIXME: json write map key ... + Object detailBean = entry.getValue(); + targetDescriptor.jsonWrite(ctx, detailBean); + } + ctx.endAssocMany(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java new file mode 100644 index 000000000..0d02ff85f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -0,0 +1,1330 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.BasicAttribute; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.InvalidValue; +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.ebean.config.ldap.LdapAttributeAdapter; +import com.avaje.ebean.text.StringFormatter; +import com.avaje.ebean.text.StringParser; +import com.avaje.ebean.text.TextException; +import com.avaje.ebean.validation.factory.Validator; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.ldap.LdapPersistenceException; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.query.SqlBeanLoad; +import com.avaje.ebeaninternal.server.reflect.BeanReflectGetter; +import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.util.ValueUtil; + +/** + * Description of a property of a bean. Includes its deployment information such + * as database column mapping information. + */ +public class BeanProperty implements ElPropertyValue { + + /** + * Advanced bean deployment. To exclude this property from update where + * clause. + */ + public static final String EXCLUDE_FROM_UPDATE_WHERE = "EXCLUDE_FROM_UPDATE_WHERE"; + + /** + * Advanced bean deployment. To exclude this property from delete where + * clause. + */ + public static final String EXCLUDE_FROM_DELETE_WHERE = "EXCLUDE_FROM_DELETE_WHERE"; + + /** + * Advanced bean deployment. To exclude this property from insert. + */ + public static final String EXCLUDE_FROM_INSERT = "EXCLUDE_FROM_INSERT"; + + /** + * Advanced bean deployment. To exclude this property from update set + * clause. + */ + public static final String EXCLUDE_FROM_UPDATE = "EXCLUDE_FROM_UPDATE"; + + /** + * Flag to mark this at part of the unique id. + */ + final boolean id; + + /** + * Flag to make this as a dummy property for unidirecitonal relationships. + */ + final boolean unidirectionalShadow; + + /** + * Flag to mark the property as embedded. This could be on + * BeanPropertyAssocOne rather than here. Put it here for checking Id type + * (embedded or not). + */ + final boolean embedded; + + /** + * Flag indicating if this the version property. + */ + final boolean version; + + final boolean naturalKey; + + /** + * Set if this property is nullable. + */ + final boolean nullable; + + final boolean unique; + + /** + * Is this property include in database resultSet. + */ + final boolean dbRead; + + /** + * Include in DB insert. + */ + final boolean dbInsertable; + + /** + * Include in DB update. + */ + final boolean dbUpdatable; + + /** + * True if the property is based on a SECONDARY table. + */ + final boolean secondaryTable; + + final TableJoin secondaryTableJoin; + final String secondaryTableJoinPrefix; + + /** + * The property is inherited from a super class. + */ + final boolean inherited; + + final Class owningType; + + final boolean local; + + /** + * True if the property is a Clob, Blob LongVarchar or LongVarbinary. + */ + final boolean lob; + + final boolean fetchEager; + + final boolean isTransient; + + /** + * The logical bean property name. + */ + final String name; + + /** + * The reflected field. + */ + final Field field; + + /** + * The bean type. + */ + final Class propertyType; + + final String dbBind; + + /** + * The database column. This can include quoted identifiers. + */ + final String dbColumn; + + final String elPlaceHolder; + final String elPlaceHolderEncrypted; + + /** + * Select part of a SQL Formula used to populate this property. + */ + final String sqlFormulaSelect; + + /** + * Join part of a SQL Formula. + */ + final String sqlFormulaJoin; + + final boolean formula; + + /** + * Set to true if stored encrypted. + */ + final boolean dbEncrypted; + + final boolean localEncrypted; + + final int dbEncryptedType; + + /** + * The jdbc data type this maps to. + */ + final int dbType; + + /** + * The default value to insert if null. + */ + final Object defaultValue; + + /** + * Extra deployment parameters. + */ + final Map extraAttributeMap; + + /** + * The method used to read the property. + */ + final Method readMethod; + + /** + * The method used to write the property. + */ + final Method writeMethod; + + /** + * Generator for insert or update timestamp etc. + */ + final GeneratedProperty generatedProperty; + + final BeanReflectGetter getter; + + final BeanReflectSetter setter; + + final BeanDescriptor descriptor; + + /** + * Used for non-jdbc native types (java.util.Date Enums etc). Converts from + * logical to jdbc types. + */ + @SuppressWarnings("rawtypes") + final ScalarType scalarType; + + /** + * For LDAP attributes that have custom conversion. + */ + final LdapAttributeAdapter ldapAttributeAdapter; + + final Validator[] validators; + + final boolean hasLocalValidators; + + boolean cascadeValidate; + + /** + * The length or precision for DB column. + */ + final int dbLength; + + /** + * The scale for DB column (decimal). + */ + final int dbScale; + + /** + * Deployment defined DB column definition. + */ + final String dbColumnDefn; + + /** + * DB Constraint (typically check constraint on enum) + */ + final String dbConstraintExpression; + + final DbEncryptFunction dbEncryptFunction; + + final boolean dynamicSubclassWithInheritance; + + int deployOrder; + + public BeanProperty(DeployBeanProperty deploy) { + this(null, null, deploy); + } + + public BeanProperty(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanProperty deploy) { + + this.descriptor = descriptor; + this.name = InternString.intern(deploy.getName()); + if (descriptor != null) { + this.dynamicSubclassWithInheritance = (descriptor.isDynamicSubclass() && descriptor.hasInheritance()); + } else { + this.dynamicSubclassWithInheritance = false; + } + this.unidirectionalShadow = deploy.isUndirectionalShadow(); + this.localEncrypted = deploy.isLocalEncrypted(); + this.dbEncrypted = deploy.isDbEncrypted(); + this.dbEncryptedType = deploy.getDbEncryptedType(); + this.dbEncryptFunction = deploy.getDbEncryptFunction(); + this.dbBind = deploy.getDbBind(); + this.dbRead = deploy.isDbRead(); + this.dbInsertable = deploy.isDbInsertable(); + this.dbUpdatable = deploy.isDbUpdateable(); + + this.secondaryTable = deploy.isSecondaryTable(); + if (secondaryTable) { + this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin(), null); + this.secondaryTableJoinPrefix = deploy.getSecondaryTableJoinPrefix(); + } else { + this.secondaryTableJoin = null; + this.secondaryTableJoinPrefix = null; + } + this.fetchEager = deploy.isFetchEager(); + this.isTransient = deploy.isTransient(); + this.nullable = deploy.isNullable(); + this.unique = deploy.isUnique(); + this.naturalKey = deploy.isNaturalKey(); + this.dbLength = deploy.getDbLength(); + this.dbScale = deploy.getDbScale(); + this.dbColumnDefn = InternString.intern(deploy.getDbColumnDefn()); + this.dbConstraintExpression = InternString.intern(deploy.getDbConstraintExpression()); + + this.inherited = false;// deploy.isInherited(); + this.owningType = deploy.getOwningType(); + this.local = deploy.isLocal(); + + this.version = deploy.isVersionColumn(); + this.embedded = deploy.isEmbedded(); + this.id = deploy.isId(); + this.generatedProperty = deploy.getGeneratedProperty(); + this.readMethod = deploy.getReadMethod(); + this.writeMethod = deploy.getWriteMethod(); + this.getter = deploy.getGetter(); + if (descriptor != null && getter == null) { + if (!unidirectionalShadow) { + String m = "Null Getter for: " + getFullBeanName(); + throw new RuntimeException(m); + } + } + this.setter = deploy.getSetter(); + + this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null); + this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin()); + this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect()); + this.formula = sqlFormulaSelect != null; + + this.extraAttributeMap = deploy.getExtraAttributeMap(); + this.defaultValue = deploy.getDefaultValue(); + this.dbType = deploy.getDbType(); + this.scalarType = deploy.getScalarType(); + this.ldapAttributeAdapter = deploy.getLdapAttributeAdapter(); + this.lob = isLobType(dbType); + this.propertyType = deploy.getPropertyType(); + this.field = deploy.getField(); + this.validators = deploy.getValidators(); + this.hasLocalValidators = (validators.length > 0); + + EntityType et = descriptor == null ? null : descriptor.getEntityType(); + this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), false, null); + this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), dbEncrypted, dbColumn); + } + + private String tableAliasIntern(BeanDescriptor descriptor, String s, boolean dbEncrypted, String dbColumn) { + if (descriptor != null) { + s = StringHelper.replaceString(s, "${ta}.", "${}"); + s = StringHelper.replaceString(s, "${ta}", "${}"); + + if (dbEncrypted) { + s = dbEncryptFunction.getDecryptSql(s); + String namedParam = ":encryptkey_" + descriptor.getBaseTable() + "___" + dbColumn; + s = StringHelper.replaceString(s, "?", namedParam); + } + } + return InternString.intern(s); + } + + /** + * Create a Matching BeanProperty with some attributes overridden. + *

+ * Primarily for supporting Embedded beans with overridden dbColumn + * mappings. + *

+ */ + public BeanProperty(BeanProperty source, BeanPropertyOverride override) { + + this.descriptor = source.descriptor; + this.name = InternString.intern(source.getName()); + this.dynamicSubclassWithInheritance = source.dynamicSubclassWithInheritance; + + this.dbColumn = InternString.intern(override.getDbColumn()); + this.sqlFormulaJoin = InternString.intern(override.getSqlFormulaJoin()); + this.sqlFormulaSelect = InternString.intern(override.getSqlFormulaSelect()); + this.formula = sqlFormulaSelect != null; + + this.fetchEager = source.fetchEager; + this.unidirectionalShadow = source.unidirectionalShadow; + this.localEncrypted = source.isLocalEncrypted(); + this.isTransient = source.isTransient(); + this.secondaryTable = source.isSecondaryTable(); + this.secondaryTableJoin = source.secondaryTableJoin; + this.secondaryTableJoinPrefix = source.secondaryTableJoinPrefix; + + this.dbBind = source.getDbBind(); + this.dbEncrypted = source.isDbEncrypted(); + this.dbEncryptedType = source.getDbEncryptedType(); + this.dbEncryptFunction = source.dbEncryptFunction; + this.dbRead = source.isDbRead(); + this.dbInsertable = source.isDbInsertable(); + this.dbUpdatable = source.isDbUpdatable(); + this.nullable = source.isNullable(); + this.unique = source.isUnique(); + this.naturalKey = source.isNaturalKey(); + this.dbLength = source.getDbLength(); + this.dbScale = source.getDbScale(); + this.dbColumnDefn = InternString.intern(source.getDbColumnDefn()); + this.dbConstraintExpression = InternString.intern(source.getDbConstraintExpression()); + + this.inherited = source.isInherited(); + this.owningType = source.owningType; + this.local = owningType.equals(descriptor.getBeanType()); + + this.version = source.isVersion(); + this.embedded = source.isEmbedded(); + this.id = source.isId(); + this.generatedProperty = source.getGeneratedProperty(); + this.readMethod = source.getReadMethod(); + this.writeMethod = source.getWriteMethod(); + this.getter = source.getter; + this.setter = source.setter; + this.extraAttributeMap = source.extraAttributeMap; + this.defaultValue = source.getDefaultValue(); + this.dbType = source.getDbType(); + this.scalarType = source.scalarType; + this.ldapAttributeAdapter = source.ldapAttributeAdapter; + this.lob = isLobType(dbType); + this.propertyType = source.getPropertyType(); + this.field = source.getField(); + this.validators = source.getValidators(); + this.hasLocalValidators = validators.length > 0; + + this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn); + this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn); + } + + /** + * Initialise the property before returning to client code. Used to + * initialise variables that can't be done in construction due to recursive + * issues. + */ + public void initialise() { + // do nothing for normal BeanProperty + if (!isTransient && scalarType == null) { + String msg = "No ScalarType assigned to " + descriptor.getFullName() + "." + getName(); + throw new RuntimeException(msg); + } + } + + /** + * Return the order this property appears in the bean. + */ + public int getDeployOrder() { + return deployOrder; + } + + /** + * Set the order this property appears in the bean. + */ + public void setDeployOrder(int deployOrder) { + this.deployOrder = deployOrder; + } + + public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, + boolean propertyDeploy) { + throw new PersistenceException("Not valid on scalar bean property " + getFullBeanName()); + } + + /** + * Return the BeanDescriptor that owns this property. + */ + public BeanDescriptor getBeanDescriptor() { + return descriptor; + } + + /** + * Return true is this is a simple scalar property. + */ + public boolean isScalar() { + return true; + } + + /** + * Return true if this property is based on a formula. + */ + public boolean isFormula() { + return formula; + } + + public boolean hasChanged(Object bean, Object oldValues) { + Object value = getValue(bean); + Object oldVal = getValue(oldValues); + + return !ValueUtil.areEqual(value, oldVal); + } + + public void copyProperty(Object sourceBean, Object destBean) { + Object value = getValue(sourceBean); + setValue(destBean, value); + } + + /** + * Return the encrypt key for the column matching this property. + */ + public EncryptKey getEncryptKey() { + return descriptor.getEncryptKey(this); + } + + public String getDecryptProperty() { + return dbEncryptFunction.getDecryptSql(this.getName()); + } + + public String getDecryptProperty(String propertyName) { + return dbEncryptFunction.getDecryptSql(propertyName); + } + + public String getDecryptSql() { + return dbEncryptFunction.getDecryptSql(this.getDbColumn()); + } + + public String getDecryptSql(String tableAlias) { + return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()); + } + + /** + * Add any extra joins required to support this property. Generally a no + * operation except for a OneToOne exported. + */ + public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + if (formula && sqlFormulaJoin != null) { + ctx.appendFormulaJoin(sqlFormulaJoin, forceOuterJoin); + + } else if (secondaryTableJoin != null) { + + String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix); + secondaryTableJoin.addJoin(forceOuterJoin, relativePrefix, ctx); + } + } + + /** + * Returns null unless this property is using a secondary table. In that + * case this returns the logical property prefix. + */ + public String getSecondaryTableJoinPrefix() { + return secondaryTableJoinPrefix; + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + if (formula) { + ctx.appendFormulaSelect(sqlFormulaSelect); + + } else if (!isTransient) { + + if (secondaryTableJoin != null) { + String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix); + ctx.pushTableAlias(relativePrefix); + } + + if (dbEncrypted) { + String decryptSql = getDecryptSql(ctx.peekTableAlias()); + ctx.appendRawColumn(decryptSql); + ctx.addEncryptedProp(this); + + } else { + ctx.appendColumn(dbColumn); + } + + if (secondaryTableJoin != null) { + ctx.popTableAlias(); + } + } + } + + public boolean isAssignableFrom(Class type) { + return owningType.isAssignableFrom(type); + } + + public Object readSetOwning(DbReadContext ctx, Object bean, Class type) throws SQLException { + + try { + Object value = scalarType.read(ctx.getDataReader()); + if (value == null || bean == null) { + // not setting the value... + } else { + if (owningType.equals(type)) { + setValue(bean, value); + } + } + return value; + } catch (Exception e) { + String msg = "Error readSet on " + descriptor + "." + name; + throw new PersistenceException(msg, e); + } + } + + public void loadIgnore(DbReadContext ctx) { + scalarType.loadIgnore(ctx.getDataReader()); + } + + public void load(SqlBeanLoad sqlBeanLoad) throws SQLException { + sqlBeanLoad.load(this); + } + + public void buildSelectExpressionChain(String prefix, List selectChain) { + if (prefix == null) { + selectChain.add(name); + } else { + selectChain.add(prefix + "." + name); + } + } + + public Object read(DbReadContext ctx) throws SQLException { + return scalarType.read(ctx.getDataReader()); + } + + public Object readSet(DbReadContext ctx, Object bean, Class type) throws SQLException { + + try { + Object value = scalarType.read(ctx.getDataReader()); + if (bean == null || (type != null && !owningType.isAssignableFrom(type))) { + // not setting the value... + } else { + setValue(bean, value); + } + return value; + } catch (Exception e) { + String msg = "Error readSet on " + descriptor + "." + name; + throw new PersistenceException(msg, e); + } + } + + /** + * Convert the type to the bean type if required. + *

+ * Generally only used to ensure id properties are converted for + * Query.setId() use. + *

+ */ + public Object toBeanType(Object value) { + return scalarType.toBeanType(value); + } + + @SuppressWarnings("unchecked") + public void bind(DataBind b, Object value) throws SQLException { + scalarType.bind(b, value); + } + + public void writeData(DataOutput dataOutput, Object value) throws IOException { + scalarType.writeData(dataOutput, value); + } + + public Object readData(DataInput dataInput) throws IOException { + return scalarType.readData(dataInput); + } + + Validator[] getValidators() { + return validators; + } + + public boolean isCascadeValidate() { + return cascadeValidate; + } + + public boolean hasLocalValidators() { + return hasLocalValidators; + } + + public boolean hasValidationRules(boolean cascade) { + return hasLocalValidators || (cascade && cascadeValidate); + } + + /** + * Checks to see if a bean is a reference (will be lazy loaded) or a + * BeanCollection that has not yet been populated. + *

+ * For base types this returns true. + *

+ */ + public boolean isValueLoaded(Object value) { + return true; + } + + /** + * Cascade the validation to the associated bean or collection. + */ + public InvalidValue validateCascade(Object value) { + return null; + } + + /** + * Validate the property with the given value. + * + * @param cascade + * if true cascade for assoc beans and collections. + * @param value + * the value to validate + * @return the list of errors that occurred. + */ + public final List validate(boolean cascade, Object value) { + + if (!isValueLoaded(value)) { + return null; + } + + ArrayList list = null; + for (int i = 0; i < validators.length; i++) { + if (!validators[i].isValid(value)) { + if (list == null) { + list = new ArrayList(); + } + Validator v = validators[i]; + list.add(new InvalidValue(v.getKey(), v.getAttributes(), descriptor.getFullName(), name, value)); + } + } + + if (list == null && cascade && cascadeValidate) { + // cascade the validation for assoc beans + InvalidValue recursive = validateCascade(value); + if (recursive != null) { + return InvalidValue.toList(recursive); + + } + } + return list; + } + + public BeanProperty getBeanProperty() { + return this; + } + + /** + * Return the getter method. + */ + public Method getReadMethod() { + return readMethod; + } + + /** + * Return the setter method. + */ + public Method getWriteMethod() { + return writeMethod; + } + + /** + * Return true if this object is part of an inheritance hierarchy. + */ + public boolean isInherited() { + return inherited; + } + + /** + * Return true is this type is not from a super type. + */ + public boolean isLocal() { + return local; + } + + public Attribute createAttribute(Object bean) { + Object v = getValue(bean); + if (v == null) { + return null; + } + if (ldapAttributeAdapter != null) { + return ldapAttributeAdapter.createAttribute(v); + } + Object ldapValue = scalarType.toJdbcType(v); + return new BasicAttribute(dbColumn, ldapValue); + } + + public void setAttributeValue(Object bean, Attribute attr) { + try { + if (attr != null) { + Object beanValue; + if (ldapAttributeAdapter != null) { + beanValue = ldapAttributeAdapter.readAttribute(attr); + } else { + beanValue = scalarType.toBeanType(attr.get()); + } + + setValue(bean, beanValue); + } + } catch (NamingException e) { + throw new LdapPersistenceException(e); + } + } + + /** + * Set the value of the property without interception or + * PropertyChangeSupport. + */ + public void setValue(Object bean, Object value) { + try { + if (bean instanceof EntityBean) { + setter.set(bean, value); + } else { + Object[] args = new Object[1]; + args[0] = value; + writeMethod.invoke(bean, args); + } + } catch (Exception ex) { + String beanType = bean == null ? "null" : bean.getClass().getName(); + String msg = "set " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType + + "] threw error"; + throw new RuntimeException(msg, ex); + } + } + + /** + * Set the value of the property. + */ + public void setValueIntercept(Object bean, Object value) { + try { + if (bean instanceof EntityBean) { + setter.setIntercept(bean, value); + } else { + Object[] args = new Object[1]; + args[0] = value; + writeMethod.invoke(bean, args); + } + } catch (Exception ex) { + String beanType = bean == null ? "null" : bean.getClass().getName(); + String msg = "setIntercept " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType + + "] threw error"; + throw new RuntimeException(msg, ex); + } + } + + private static Object[] NO_ARGS = new Object[0]; + + /** + * Return the property value taking inheritance into account. + */ + public Object getValueWithInheritance(Object bean) { + if (dynamicSubclassWithInheritance) { + return descriptor.getBeanPropertyWithInheritance(bean, name); + } + return getValue(bean); + } + + public Object getCacheDataValue(Object bean){ + return getValue(bean); + } + + public void setCacheDataValue(Object bean, Object cacheData, Object oldValues, boolean readOnly){ + setValue(bean, cacheData); + } + + /** + * Return the value of the property method. + */ + public Object getValue(Object bean) { + try { + if (bean instanceof EntityBean) { + return getter.get(bean); + } else { + return readMethod.invoke(bean, NO_ARGS); + } + } catch (Exception ex) { + String beanType = bean == null ? "null" : bean.getClass().getName(); + String msg = "get " + name + " on [" + descriptor + "] type[" + beanType + "] threw error."; + throw new RuntimeException(msg, ex); + } + } + + /** + * Explicitly use reflection to get value. + */ + public Object getValueViaReflection(Object bean) { + try { + return readMethod.invoke(bean, NO_ARGS); + } catch (Exception ex) { + String beanType = bean == null ? "null" : bean.getClass().getName(); + String msg = "get " + name + " on [" + descriptor + "] type[" + beanType + "] threw error."; + throw new RuntimeException(msg, ex); + } + } + + public Object getValueIntercept(Object bean) { + try { + if (bean instanceof EntityBean) { + return getter.getIntercept(bean); + } else { + return readMethod.invoke(bean, NO_ARGS); + } + } catch (Exception ex) { + String beanType = bean == null ? "null" : bean.getClass().getName(); + String msg = "getIntercept " + name + " on [" + descriptor + "] type[" + beanType + "] threw error."; + throw new RuntimeException(msg, ex); + } + } + + public Object elConvertType(Object value) { + if (value == null) { + return null; + } + return convertToLogicalType(value); + } + + public void elSetReference(Object bean) { + throw new RuntimeException("Should not be called"); + } + + public void elSetValue(Object bean, Object value, boolean populate, boolean reference) { + if (bean != null) { + setValueIntercept(bean, value); + } + } + + public Object elGetValue(Object bean) { + if (bean == null) { + return null; + } + return getValueIntercept(bean); + } + + public Object elGetReference(Object bean) { + throw new RuntimeException("Not expected to call this"); + } + + /** + * Return the name of the property. + */ + public String getName() { + return name; + } + + public String getElName() { + return name; + } + + /** + * This is a full ElGetValue. + */ + public boolean isDeployOnly() { + return false; + } + + public boolean containsManySince(String sinceProperty) { + return containsMany(); + } + + public boolean containsMany() { + return false; + } + + public Object[] getAssocOneIdValues(Object bean) { + // Returns null as not an AssocOne. + return null; + } + + public String getAssocOneIdExpr(String prefix, String operator) { + // Returns null as not an AssocOne. + return null; + } + + public String getAssocIdInExpr(String prefix) { + // Returns null as not an AssocOne. + return null; + } + + public String getAssocIdInValueExpr(int size) { + // Returns null as not an AssocOne. + return null; + } + + public boolean isAssocId() { + // Returns false - override in BeanPropertyAssocOne. + return false; + } + + public boolean isAssocProperty() { + // Returns false - override in BeanPropertyAssocOne. + return false; + } + + public String getElPlaceholder(boolean encrypted) { + return encrypted ? elPlaceHolderEncrypted : elPlaceHolder; + } + + public String getElPrefix() { + return secondaryTableJoinPrefix; + } + + /** + * Return the full name of this property. + */ + public String getFullBeanName() { + return descriptor.getFullName() + "." + name; + } + + /** + * Return the scalarType. + */ + public ScalarType getScalarType() { + return scalarType; + } + + public StringFormatter getStringFormatter() { + return scalarType; + } + + public StringParser getStringParser() { + return scalarType; + } + + public boolean isDateTimeCapable() { + return scalarType != null && scalarType.isDateTimeCapable(); + } + + public int getJdbcType() { + return scalarType == null ? 0 : scalarType.getJdbcType(); + } + + public Object parseDateTime(long systemTimeMillis) { + return scalarType.parseDateTime(systemTimeMillis); + } + + /** + * Return the DB max length (varchar) or precision (decimal). + */ + public int getDbLength() { + return dbLength; + } + + /** + * Return the DB scale for numeric columns. + */ + public int getDbScale() { + return dbScale; + } + + /** + * Return a specific column DDL definition if specified (otherwise null). + */ + public String getDbColumnDefn() { + return dbColumnDefn; + } + + /** + * Return the DB constraint expression (can be null). + *

+ * For an Enum returns IN expression for the set of Enum values. + *

+ */ + public String getDbConstraintExpression() { + return dbConstraintExpression; + } + + /** + * Return the DB column type definition. + */ + public String renderDbType(DbType dbType) { + if (dbColumnDefn != null) { + return dbColumnDefn; + } + return dbType.renderType(dbLength, dbScale); + } + + /** + * Return the bean Field associated with this property. + */ + public Field getField() { + return field; + } + + /** + * Return the GeneratedValue. Used to generate update timestamp etc. + */ + public GeneratedProperty getGeneratedProperty() { + return generatedProperty; + } + + /** + * Return true if this is the natural key property. + */ + public boolean isNaturalKey() { + return naturalKey; + } + + /** + * Return true if this property is mandatory. + */ + public boolean isNullable() { + return nullable; + } + + /** + * Return true if DDL Not NULL constraint should be defined for this column + * based on it being a version column or having a generated property. + */ + public boolean isDDLNotNull() { + return isVersion() || (generatedProperty != null && generatedProperty.isDDLNotNullable()); + } + + /** + * Return true if the DB column should be unique. + */ + public boolean isUnique() { + return unique; + } + + /** + * Return true if the property is transient. + */ + public boolean isTransient() { + return isTransient; + } + + /** + * Return true if this is a version column used for concurrency checking. + */ + public boolean isVersion() { + return version; + } + + public String getDeployProperty() { + return dbColumn; + } + + /** + * The database column name this is mapped to. + */ + public String getDbColumn() { + return dbColumn; + } + + /** + * Return the database jdbc data type this is mapped to. + */ + public int getDbType() { + return dbType; + } + + /** + * Perform DB to Logical type conversion (if necessary). + */ + public Object convertToLogicalType(Object value) { + if (scalarType != null) { + return scalarType.toBeanType(value); + } + return value; + } + +// private ArrayList luceneIndexes; +// +// public void registerLuceneIndex(LuceneIndex luceneIndex) { +// if (luceneIndexes == null) { +// luceneIndexes = new ArrayList(); +// } +// luceneIndexes.add(luceneIndex); +// } +// +// public boolean isDeltaRequired() { +// return true;//luceneIndexes != null; +// } + + /** + * Return true if by default this property is set to fetch eager. + * Lob's usually default to fetch lazy. + */ + public boolean isFetchEager() { + return fetchEager; + } + + /** + * Return true if this is mapped to a Clob Blob LongVarchar or + * LongVarbinary. + */ + public boolean isLob() { + return lob; + } + + private boolean isLobType(int type) { + switch (type) { + case Types.CLOB: + return true; + case Types.BLOB: + return true; + case Types.LONGVARBINARY: + return true; + case Types.LONGVARCHAR: + return true; + + default: + return false; + } + } + + /** + * Return the DB bind parameter. Typically is "?" but different for + * encrypted bind. + */ + public String getDbBind() { + return dbBind; + } + + /** + * Returns true if DB encrypted. + */ + public boolean isLocalEncrypted() { + return localEncrypted; + } + + /** + * Return true if this property is stored encrypted. + */ + public boolean isDbEncrypted() { + return dbEncrypted; + } + + public int getDbEncryptedType() { + return dbEncryptedType; + } + + /** + * Return true if this property should be included in an Insert. + */ + public boolean isDbInsertable() { + return dbInsertable; + } + + /** + * Return true if this property should be included in an Update. + */ + public boolean isDbUpdatable() { + return dbUpdatable; + } + + /** + * Return true if this property is included in database queries. + */ + public boolean isDbRead() { + return dbRead; + } + + /** + * Return true if this property is based on a secondary table (not the base + * table). + */ + public boolean isSecondaryTable() { + return secondaryTable; + } + + /** + * Return the property type. + */ + public Class getPropertyType() { + return propertyType; + } + + /** + * Return true if this is included in the unique id. + */ + public boolean isId() { + return id; + } + + /** + * Return true if this is an Embedded property. In this case it shares the + * table and primary key of its owner object. + */ + public boolean isEmbedded() { + return embedded; + } + + /** + * Return an extra attribute set on this property. + */ + public String getExtraAttribute(String key) { + return extraAttributeMap.get(key); + } + + /** + * Return the default value. + */ + public Object getDefaultValue() { + return defaultValue; + } + + public String toString() { + return name; + } + + @SuppressWarnings("unchecked") + public void jsonWrite(WriteJsonContext ctx, Object bean) { + + Object value = getValueIntercept(bean); + if (value == null) { + ctx.appendNull(name); + } else { + ctx.appendNameValue(name, scalarType, value); + } + } + + public void jsonRead(ReadJsonContext ctx, Object bean) { + + String jsonValue; + try { + jsonValue = ctx.readScalarValue(); + } catch (TextException e){ + throw new TextException("Error reading property "+getFullBeanName(), e); + } + Object objValue; + if (jsonValue == null) { + objValue = null; + } else { + objValue = scalarType.jsonFromString(jsonValue, ctx.getValueAdapter()); + } + setValue(bean, objValue); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java new file mode 100644 index 000000000..dd1658bc1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -0,0 +1,402 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.deploy.id.ImportedIdEmbedded; +import com.avaje.ebeaninternal.server.deploy.id.ImportedIdMultiple; +import com.avaje.ebeaninternal.server.deploy.id.ImportedIdSimple; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; +import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Abstract base for properties mapped to an associated bean, list, set or map. + */ +public abstract class BeanPropertyAssoc extends BeanProperty { + + private static final Logger logger = Logger.getLogger(BeanPropertyAssoc.class.getName()); + + /** + * The descriptor of the target. This MUST be initialised after construction + * so as to avoid a dependency loop between BeanDescriptors. + */ + BeanDescriptor targetDescriptor; + + IdBinder targetIdBinder; + + InheritInfo targetInheritInfo; + + String targetIdProperty; + + /** + * Persist settings. + */ + final BeanCascadeInfo cascadeInfo; + + /** + * Join between the beans. + */ + final TableJoin tableJoin; + + /** + * The type of the joined bean. + */ + final Class targetType; + + /** + * The join table information. + */ + final BeanTable beanTable; + + final String mappedBy; + + /** + * Whether the associated join type should be an outer join. + */ + final boolean isOuterJoin; + + String extraWhere; + + boolean saveRecurseSkippable; + + boolean deleteRecurseSkippable; + + /** + * Construct the property. + */ + public BeanPropertyAssoc(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanPropertyAssoc deploy) { + super(owner, descriptor, deploy); + this.extraWhere = InternString.intern(deploy.getExtraWhere()); + this.isOuterJoin = deploy.isOuterJoin(); + this.beanTable = deploy.getBeanTable(); + this.mappedBy = InternString.intern(deploy.getMappedBy()); + + this.tableJoin = new TableJoin(deploy.getTableJoin(), null); + + this.targetType = deploy.getTargetType(); + this.cascadeInfo = deploy.getCascadeInfo(); + } + + /** + * Initialise post construction. + */ + @Override + public void initialise() { + // this *MUST* execute after the BeanDescriptor is + // put into the map to stop infinite recursion + if (!isTransient){ + targetDescriptor = descriptor.getBeanDescriptor(targetType); + targetIdBinder = targetDescriptor.getIdBinder(); + targetInheritInfo = targetDescriptor.getInheritInfo(); + + saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable(); + deleteRecurseSkippable = targetDescriptor.isDeleteRecurseSkippable(); + + cascadeValidate = cascadeInfo.isValidate(); + + if (!targetIdBinder.isComplexId()){ + targetIdProperty = targetIdBinder.getIdProperty(); + } + } + } + + /** + * Create a ElPropertyValue for a *ToOne or *ToMany. + */ + protected ElPropertyValue createElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) { + + // associated or embedded bean + BeanDescriptor embDesc = getTargetDescriptor(); + + if (chain == null) { + chain = new ElPropertyChainBuilder(isEmbedded(), propName); + } + chain.add(this); + if (containsMany()) { + chain.setContainsMany(true); + } + return embDesc.buildElGetValue(remainder, chain, propertyDeploy); + } + + /** + * Add table join with table alias based on prefix. + */ + public boolean addJoin(boolean forceOuterJoin, String prefix, DbSqlContext ctx) { + return tableJoin.addJoin(forceOuterJoin, prefix, ctx); + } + + /** + * Add table join with explicit table alias. + */ + public boolean addJoin(boolean forceOuterJoin, String a1, String a2, DbSqlContext ctx) { + return tableJoin.addJoin(forceOuterJoin, a1, a2, ctx); + } + + /** + * Add table join with explicit table alias. + */ + public void addInnerJoin(String a1, String a2, DbSqlContext ctx) { + tableJoin.addInnerJoin(a1, a2, ctx); + } + + /** + * Return false. + */ + public boolean isScalar() { + return false; + } + + /** + * Return the mappedBy property. + * This will be null on the owning side. + */ + public String getMappedBy() { + return mappedBy; + } + + /** + * Return the Id property of the target entity type. + *

+ * This will return null for multiple Id properties. + *

+ */ + public String getTargetIdProperty() { + return targetIdProperty; + } + + /** + * Return the BeanDescriptor of the target. + */ + public BeanDescriptor getTargetDescriptor() { + return targetDescriptor; + } + + public boolean isSaveRecurseSkippable(Object bean) { + if (!saveRecurseSkippable){ + // we have to saveRecurse even if the bean is not dirty + // as this bean has cascade save on some of its properties + return false; + } + if (bean instanceof EntityBean){ + return !((EntityBean)bean)._ebean_getIntercept().isNewOrDirty(); + } else { + // we don't know so we say no + return false; + } + } + + /** + * Return true if save can be skipped for unmodified bean(s) of this + * property. + *

+ * That is, if a bean of this property is unmodified we don't need to + * saveRecurse because none of its associated beans have cascade save set to + * true. + *

+ */ + public boolean isSaveRecurseSkippable() { + return saveRecurseSkippable; + } + + /** + * Similar to isSaveRecurseSkippable but in terms of delete. + */ + public boolean isDeleteRecurseSkippable() { + return deleteRecurseSkippable; + } + + /** + * Return true if the unique id properties are all not null for this bean. + */ + public boolean hasId(Object bean) { + + BeanDescriptor targetDesc = getTargetDescriptor(); + + BeanProperty[] uids = targetDesc.propertiesId(); + for (int i = 0; i < uids.length; i++) { + + Object value = uids[i].getValue(bean); + if (value == null) { + return false; + } + } + // all the unique properties are non-null + return true; + } + + /** + * Return the type of the target. + *

+ * This is the class of the associated bean, or beans contained in a list, + * set or map. + *

+ */ + public Class getTargetType() { + return targetType; + } + + /** + * Return an extra clause to add to the query for loading or joining + * to this bean type. + */ + public String getExtraWhere() { + return extraWhere; + } + + /** + * Return if this association should use an Outer join. + */ + public boolean isOuterJoin() { + return isOuterJoin; + } + + /** + * Return true if this association is updateable. + */ + public boolean isUpdateable() { + if (tableJoin.columns().length > 0) { + return tableJoin.columns()[0].isUpdateable(); + } + + return true; + } + + /** + * Return true if this association is insertable. + */ + public boolean isInsertable() { + if (tableJoin.columns().length > 0) { + return tableJoin.columns()[0].isInsertable(); + } + + return true; + } + + /** + * return the join to use for the bean. + */ + public TableJoin getTableJoin() { + return tableJoin; + } + + /** + * Return the BeanTable for this association. + *

+ * This has the table name which is used to determine the relationship for + * this association. + *

+ */ + public BeanTable getBeanTable() { + return beanTable; + } + + /** + * Get the persist info. + */ + public BeanCascadeInfo getCascadeInfo() { + return cascadeInfo; + } + + /** + * Build the list of imported property. Matches BeanProperty from the target + * descriptor back to local database columns in the TableJoin. + */ + protected ImportedId createImportedId(BeanPropertyAssoc owner, BeanDescriptor target, TableJoin join) { + + BeanProperty[] props = target.propertiesId(); + BeanProperty[] others = target.propertiesBaseScalar(); + + if (descriptor.isSqlSelectBased()){ + String dbColumn = owner.getDbColumn(); + return new ImportedIdSimple(owner, dbColumn, props[0], 0); + } + + TableJoinColumn[] cols = join.columns(); + + if (props.length == 1) { + if (!props[0].isEmbedded()) { + // simple single scalar id + if (cols.length != 1){ + String msg = "No Imported Id column for ["+props[0]+"] in table ["+join.getTable()+"]"; + logger.log(Level.SEVERE, msg); + return null; + } else { + return createImportedScalar(owner, cols[0], props, others); + } + } else { + // embedded id + BeanPropertyAssocOne embProp = (BeanPropertyAssocOne)props[0]; + BeanProperty[] embBaseProps = embProp.getTargetDescriptor().propertiesBaseScalar(); + ImportedIdSimple[] scalars = createImportedList(owner, cols, embBaseProps, others); + + return new ImportedIdEmbedded(owner, embProp, scalars); + } + + } else { + // Concatenated key that is not embedded + ImportedIdSimple[] scalars = createImportedList(owner, cols, props, others); + return new ImportedIdMultiple(owner, scalars); + } + } + + private ImportedIdSimple[] createImportedList(BeanPropertyAssoc owner, TableJoinColumn[] cols, BeanProperty[] props, BeanProperty[] others) { + + ArrayList list = new ArrayList(); + + for (int i = 0; i < cols.length; i++) { + list.add(createImportedScalar(owner, cols[i], props, others)); + } + + return ImportedIdSimple.sort(list); + } + + private ImportedIdSimple createImportedScalar(BeanPropertyAssoc owner, TableJoinColumn col, BeanProperty[] props, BeanProperty[] others) { + + String matchColumn = col.getForeignDbColumn(); + String localColumn = col.getLocalDbColumn(); + + for (int j = 0; j < props.length; j++) { + if (props[j].getDbColumn().equalsIgnoreCase(matchColumn)) { + return new ImportedIdSimple(owner, localColumn, props[j], j); + } + } + + for (int j = 0; j < others.length; j++) { + if (others[j].getDbColumn().equalsIgnoreCase(matchColumn)) { + return new ImportedIdSimple(owner, localColumn, others[j], j+props.length); + } + } + + String msg = "Error with the Join on ["+getFullBeanName() + +"]. Could not find the local match for ["+matchColumn+"] "//in table["+searchTable+"]?" + +" Perhaps an error in a @JoinColumn"; + throw new PersistenceException(msg); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java new file mode 100644 index 000000000..a4925a766 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -0,0 +1,845 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Expression; +import com.avaje.ebean.InvalidValue; +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.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.query.SqlBeanLoad; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext.ReadBeanState; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; + +/** + * Property mapped to a List Set or Map. + */ +public class BeanPropertyAssocMany extends BeanPropertyAssoc { + + /** + * Join for manyToMany intersection table. + */ + final TableJoin intersectionJoin; + + /** + * For ManyToMany this is the Inverse join used to build reference queries. + */ + final TableJoin inverseJoin; + + /** + * Flag to indicate that this is a unidirectional relationship. + */ + final boolean unidirectional; + + /** + * Flag to indicate manyToMany relationship. + */ + final boolean manyToMany; + + final String fetchOrderBy; + + final String mapKey; + + /** + * The type of the many, set, list or map. + */ + final ManyType manyType; + + final String serverName; + + final ModifyListenMode modifyListenMode; + + BeanProperty mapKeyProperty; + /** + * Derived list of exported property and matching foreignKey + */ + ExportedProperty[] exportedProperties; + + /** + * Property on the 'child' bean that links back to the 'master'. + */ + BeanPropertyAssocOne childMasterProperty; + + boolean embeddedExportedProperties; + + BeanCollectionHelp help; + + ImportedId importedId; + + String deleteByParentIdSql; + String deleteByParentIdInSql; + + + final CollectionTypeConverter typeConverter; + + /** + * Create this property. + */ + public BeanPropertyAssocMany(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanPropertyAssocMany deploy) { + super(owner, descriptor, deploy); + this.unidirectional = deploy.isUnidirectional(); + this.manyToMany = deploy.isManyToMany(); + this.serverName = descriptor.getServerName(); + this.manyType = deploy.getManyType(); + this.typeConverter = manyType.getTypeConverter(); + this.mapKey = deploy.getMapKey(); + this.fetchOrderBy = deploy.getFetchOrderBy(); + + this.intersectionJoin = deploy.createIntersectionTableJoin(); + this.inverseJoin = deploy.createInverseTableJoin(); + this.modifyListenMode = deploy.getModifyListenMode(); + } + + public void initialise() { + super.initialise(); + + if (!isTransient){ + this.help = BeanCollectionHelpFactory.create(this); + + if (manyToMany){ + // only manyToMany's have imported properties + importedId = createImportedId(this, targetDescriptor, tableJoin); + + } else { + // find the property in the many that matches + // back to the master (Order in the OrderDetail bean) + childMasterProperty = initChildMasterProperty(); + if (childMasterProperty != null){ + childMasterProperty.setRelationshipProperty(this); + } + } + + if (mapKey != null){ + mapKeyProperty = initMapKeyProperty(); + } + + exportedProperties = createExported(); + if (exportedProperties.length > 0){ + embeddedExportedProperties = exportedProperties[0].isEmbedded(); + } + + String delStmt; + if (manyToMany){ + delStmt = "delete from "+inverseJoin.getTable()+" where "; + } else { + delStmt = "delete from "+targetDescriptor.getBaseTable()+" where "; + } + deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false); + deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true); + } + } + + /** + * Get the underlying List Set or Map. + * For unwrapping scala collection types etc. + */ + public Object getValueUnderlying(Object bean) { + + Object value = getValue(bean); + if (typeConverter != null){ + value = typeConverter.toUnderlying(value); + } + return value; + } + + @Override + public Object getValue(Object bean) { + return super.getValue(bean); + } + + @Override + public Object getValueIntercept(Object bean) { + return super.getValueIntercept(bean); + } + + @Override + public void setValue(Object bean, Object value) { + if (typeConverter != null){ + value = typeConverter.toWrapped(value); + } + super.setValue(bean, value); + } + + @Override + public void setValueIntercept(Object bean, Object value) { + if (typeConverter != null){ + value = typeConverter.toWrapped(value); + } + super.setValueIntercept(bean, value); + } + + public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) { + return createElPropertyValue(propName, remainder, chain, propertyDeploy); + } + + public SqlUpdate deleteByParentId(Object parentId, List parentIdist) { + if (parentId != null){ + return deleteByParentId(parentId); + } else { + return deleteByParentIdList(parentIdist); + } + } + + private SqlUpdate deleteByParentId(Object parentId) { + DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql); + bindWhereParendId(sqlDelete, parentId); + return sqlDelete; + } + + /** + * Find the Id's of detail beans given a parent Id or list of parent Id's. + */ + public List findIdsByParentId(Object parentId, List parentIdist, Transaction t, ArrayList excludeDetailIds) { + if (parentId != null){ + return findIdsByParentId(parentId, t, excludeDetailIds); + } else { + return findIdsByParentIdList(parentIdist, t, excludeDetailIds); + } + } + + private List findIdsByParentId(Object parentId, Transaction t, ArrayList excludeDetailIds) { + + String rawWhere = deriveWhereParentIdSql(false); + + EbeanServer server = getBeanDescriptor().getEbeanServer(); + Query q = server.find(getPropertyType()) + .where().raw(rawWhere).query(); + + bindWhereParendId(1, q, parentId); + + if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) { + Expression idIn = q.getExpressionFactory().idIn(excludeDetailIds); + q.where().not(idIn); + } + + return server.findIds(q, t); + } + + private List findIdsByParentIdList(List parentIdist, Transaction t, ArrayList excludeDetailIds) { + + String rawWhere = deriveWhereParentIdSql(true); + String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size()); + + String expr = rawWhere+inClause; + + EbeanServer server = getBeanDescriptor().getEbeanServer(); + Query q = server.find(getPropertyType()) + .where().raw(expr).query(); + + int pos = 1; + for (int i = 0; i < parentIdist.size(); i++) { + pos = bindWhereParendId(pos, q, parentIdist.get(i)); + } + + if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) { + Expression idIn = q.getExpressionFactory().idIn(excludeDetailIds); + q.where().not(idIn); + } + + return server.findIds(q, t); + } + + private SqlUpdate deleteByParentIdList(List parentIdist) { + + StringBuilder sb = new StringBuilder(100); + sb.append(deleteByParentIdInSql); + + String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size()); + sb.append(inClause); + + DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString()); + for (int i = 0; i < parentIdist.size(); i++) { + bindWhereParendId(delete, parentIdist.get(i)); + } + + return delete; + } + + /** + * Set the lazy load server to help create reference collections (that lazy + * load on demand). + */ + public void setLoader(BeanCollectionLoader loader){ + if (help != null){ + help.setLoader(loader); + } + } + + /** + * Return the mode for listening to modifications to collections for this + * association. + */ + public ModifyListenMode getModifyListenMode() { + return modifyListenMode; + } + + /** + * Ignore changes for Many properties. + */ + public boolean hasChanged(Object bean, Object oldValues) { + return false; + } + + @Override + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + } + + @Override + public void loadIgnore(DbReadContext ctx) { + // nothing to ignore for Many + } + + @Override + public void load(SqlBeanLoad sqlBeanLoad) throws SQLException { + sqlBeanLoad.loadAssocMany(this); + } + + @Override + public Object readSet(DbReadContext ctx, Object bean, Class type) throws SQLException { + return null; + } + + @Override + public Object read(DbReadContext ctx) throws SQLException { + return null; + } + + + @Override + public boolean isValueLoaded(Object value) { + if (value instanceof BeanCollection){ + return ((BeanCollection)value).isPopulated(); + } + return true; + } + + public void add(BeanCollection collection, Object bean) { + help.add(collection, bean); + } + + @Override + public InvalidValue validateCascade(Object manyValue) { + + ArrayList errs = help.validate(manyValue); + + if (errs == null){ + return null; + } else { + return new InvalidValue("recurse.many", targetDescriptor.getFullName(), manyValue, InvalidValue.toArray(errs)); + } + } + + /** + * Refresh the appropriate list set or map. + */ + public void refresh(EbeanServer server, Query query, Transaction t, Object parentBean) { + help.refresh(server, query, t, parentBean); + } + + /** + * Apply the refreshed BeanCollection to the property of the parentBean. + */ + public void refresh(BeanCollection bc, Object parentBean) { + help.refresh(bc, parentBean); + } + + /** + * Return the Id values from the given bean. + */ + @Override + public Object[] getAssocOneIdValues(Object bean) { + return targetDescriptor.getIdBinder().getIdValues(bean); + } + + /** + * Return the Id expression to add to where clause etc. + */ + public String getAssocOneIdExpr(String prefix, String operator) { + return targetDescriptor.getIdBinder().getAssocOneIdExpr(prefix, operator); + } + + /** + * Return the logical id value expression taking into account embedded id's. + */ + @Override + public String getAssocIdInValueExpr(int size){ + return targetDescriptor.getIdBinder().getIdInValueExpr(size); + } + + /** + * Return the logical id in expression taking into account embedded id's. + */ + @Override + public String getAssocIdInExpr(String prefix){ + return targetDescriptor.getIdBinder().getAssocIdInExpr(prefix); + } + + + @Override + public boolean isAssocId() { + return true; + } + + @Override + public boolean isAssocProperty() { + return true; + } + + /** + * Returns true. + */ + @Override + public boolean containsMany(){ + return true; + } + + /** + * Return the many type. + */ + public ManyType getManyType() { + return manyType; + } + + /** + * Return true if this is many to many. + */ + public boolean isManyToMany() { + return manyToMany; + } + + /** + * ManyToMany only, join from local table to intersection table. + */ + public TableJoin getIntersectionTableJoin() { + return intersectionJoin; + } + + /** + * Set the join properties from the parent bean to the child bean. + * This is only valid for OneToMany and NOT valid for ManyToMany. + */ + public void setJoinValuesToChild(Object parent, Object child, Object mapKeyValue) { + + if (mapKeyProperty != null){ + mapKeyProperty.setValue(child, mapKeyValue); + } + + if (!manyToMany){ + if (childMasterProperty != null){ + // bidirectional in the sense that the 'master' property + // exists on the 'detail' bean + childMasterProperty.setValue(child, parent); + } else { + // unidirectional in the sense that the 'master' property + // does NOT exist on the 'detail' bean + } + } + } + + /** + * Return the order by clause used to order the fetching of the data for + * this list, set or map. + */ + public String getFetchOrderBy() { + return fetchOrderBy; + } + + /** + * Return the default mapKey when returning a Map. + */ + public String getMapKey() { + return mapKey; + } + + public BeanCollection createReferenceIfNull(Object parentBean) { + + Object v = getValue(parentBean); + if (v instanceof BeanCollection){ + BeanCollection bc = (BeanCollection)v; + return bc.isReference() ? bc : null; + } else { + return createReference(parentBean); + } + } + + public BeanCollection createReference(Object parentBean) { + + BeanCollection ref = help.createReference(parentBean, name); + setValue(parentBean, ref); + return ref; + } + + public Object createEmpty(boolean vanilla) { + return help.createEmpty(vanilla); + } + + public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { + return help.getBeanCollectionAdd(bc, mapKey); + } + + public Object getParentId(Object parentBean) { + return descriptor.getId(parentBean); + } + + private void bindWhereParendId(DefaultSqlUpdate sqlUpd, Object parentId){ + + if (exportedProperties.length == 1){ + sqlUpd.addParameter(parentId); + return; + } + for (int i = 0; i < exportedProperties.length; i++) { + Object embVal = exportedProperties[i].getValue(parentId); + sqlUpd.addParameter(embVal); + } + } + + private int bindWhereParendId(int pos, Query q, Object parentId) { + + if (exportedProperties.length == 1) { + q.setParameter(pos++, parentId); + + } else { + + for (int i = 0; i < exportedProperties.length; i++) { + Object embVal = exportedProperties[i].getValue(parentId); + q.setParameter(pos++, embVal); + } + } + return pos; + } + + private String deriveWhereParentIdSql(boolean inClause) { + + StringBuilder sb = new StringBuilder(); + + if (inClause){ + sb.append("("); + } + for (int i = 0; i < exportedProperties.length; i++) { + String fkColumn = exportedProperties[i].getForeignDbColumn(); + if (i > 0){ + String s = inClause ? "," : " and "; + sb.append(s); + } + sb.append(fkColumn); + if (!inClause){ + sb.append("=? "); + } + } + if (inClause){ + sb.append(")"); + } + return sb.toString(); + } + + public void setPredicates(SpiQuery query, Object parentBean) { + + if (manyToMany){ + // for ManyToMany lazy loading we need to include a + // join to the intersection table. The predicate column + // is not on the 'destination many table'. + query.setIncludeTableJoin(inverseJoin); + } + + if (embeddedExportedProperties) { + // use the EmbeddedId object instead of the parentBean + BeanProperty[] uids = descriptor.propertiesId(); + parentBean = uids[0].getValue(parentBean); + } + + for (int i = 0; i < exportedProperties.length; i++) { + Object val = exportedProperties[i].getValue(parentBean); + String fkColumn = exportedProperties[i].getForeignDbColumn(); + if (!manyToMany){ + fkColumn = targetDescriptor.getBaseTableAlias()+"."+fkColumn; + } else { + // use hard coded alias for intersection table + fkColumn = "int_."+fkColumn; + } + query.where().eq(fkColumn, val); + } + + if (extraWhere != null){ + // replace the table alias place holder + String ta = targetDescriptor.getBaseTableAlias(); + String where = StringHelper.replaceString(extraWhere, "${ta}", ta); + query.where().raw(where); + } + + if (fetchOrderBy != null){ + query.order(fetchOrderBy); + } + } + + /** + * Create the array of ExportedProperty used to build reference objects. + */ + private ExportedProperty[] createExported() { + + BeanProperty[] uids = descriptor.propertiesId(); + + ArrayList list = new ArrayList(); + + if (uids.length == 1 && uids[0].isEmbedded()) { + + BeanPropertyAssocOne one = (BeanPropertyAssocOne) uids[0]; + BeanDescriptor targetDesc = one.getTargetDescriptor(); + BeanProperty[] emIds = targetDesc.propertiesBaseScalar(); + try { + for (int i = 0; i < emIds.length; i++) { + ExportedProperty expProp = findMatch(true, emIds[i]); + list.add(expProp); + } + } catch (PersistenceException e){ + // not found as individual scalar properties + e.printStackTrace(); + } + + } else { + for (int i = 0; i < uids.length; i++) { + ExportedProperty expProp = findMatch(false, uids[i]); + list.add(expProp); + } + } + + return (ExportedProperty[]) list.toArray(new ExportedProperty[list.size()]); + } + + /** + * Find the matching foreignDbColumn for a given local property. + */ + private ExportedProperty findMatch(boolean embedded,BeanProperty prop) { + + String matchColumn = prop.getDbColumn(); + + String searchTable; + TableJoinColumn[] columns; + if (manyToMany){ + // look for column going to intersection + columns = intersectionJoin.columns(); + searchTable = intersectionJoin.getTable(); + + } else { + columns = tableJoin.columns(); + searchTable = tableJoin.getTable(); + } + for (int i = 0; i < columns.length; i++) { + String matchTo = columns[i].getLocalDbColumn(); + + if (matchColumn.equalsIgnoreCase(matchTo)) { + String foreignCol = columns[i].getForeignDbColumn(); + return new ExportedProperty(embedded, foreignCol, prop); + } + } + + String msg = "Error with the Join on ["+getFullBeanName() + +"]. Could not find the matching foreign key for ["+matchColumn+"] in table["+searchTable+"]?" + +" Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?"; + throw new PersistenceException(msg); + } + + /** + * Return the child property that links back to the master bean. + *

+ * Note that childMasterProperty will be null if a field is used instead of + * a ManyToOne bean association. + *

+ */ + private BeanPropertyAssocOne initChildMasterProperty() { + + if (unidirectional){ + return null; + } + + // search for the property, to see if it exists + Class beanType = descriptor.getBeanType(); + BeanDescriptor targetDesc = getTargetDescriptor(); + + BeanPropertyAssocOne[] ones = targetDesc.propertiesOne(); + for (int i = 0; i < ones.length; i++) { + BeanPropertyAssocOne prop = ones[i]; + if (mappedBy != null){ + // match using mappedBy as property name + if (mappedBy.equalsIgnoreCase(prop.getName())) { + return prop; + } + } else { + // assume only one property that matches parent object type + if (prop.getTargetType().equals(beanType)) { + // found it, stop search + return prop; + } + } + } + + String msg = "Can not find Master [" + beanType + "] in Child[" + targetDesc + "]"; + throw new RuntimeException(msg); + } + + /** + * Search for and return the mapKey property. + */ + private BeanProperty initMapKeyProperty() { + + // search for the property + + BeanDescriptor targetDesc = getTargetDescriptor(); + + Iterator it = targetDesc.propertiesAll(); + while (it.hasNext()){ + BeanProperty prop = it.next(); + if (mapKey.equalsIgnoreCase(prop.getName())) { + return prop; + } + } + + String from = descriptor.getFullName(); + String to = targetDesc.getFullName(); + String msg = from+": Could not find mapKey property ["+mapKey+"] on ["+to+"]"; + throw new PersistenceException(msg); + } + + public IntersectionRow buildManyDeleteChildren(Object parentBean, ArrayList excludeDetailIds) { + + IntersectionRow row = new IntersectionRow(tableJoin.getTable()); + if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) { + row.setExcludeIds(excludeDetailIds, getTargetDescriptor()); + } + buildExport(row, parentBean); + return row; + } + + public IntersectionRow buildManyToManyDeleteChildren(Object parentBean) { + + IntersectionRow row = new IntersectionRow(intersectionJoin.getTable()); + buildExport(row, parentBean); + return row; + } + + public IntersectionRow buildManyToManyMapBean(Object parent, Object other) { + + IntersectionRow row = new IntersectionRow(intersectionJoin.getTable()); + + buildExport(row, parent); + buildImport(row, other); + return row; + } + + private void buildExport(IntersectionRow row, Object parentBean) { + + if (embeddedExportedProperties) { + BeanProperty[] uids = descriptor.propertiesId(); + parentBean = uids[0].getValue(parentBean); + } + for (int i = 0; i < exportedProperties.length; i++) { + Object val = exportedProperties[i].getValue(parentBean); + String fkColumn = exportedProperties[i].getForeignDbColumn(); + + row.put(fkColumn, val); + } + } + + /** + * Set the predicates for lazy loading of the association. + * Handles predicates for both OneToMany and ManyToMany. + */ + private void buildImport(IntersectionRow row, Object otherBean) { + + importedId.buildImport(row, otherBean); + } + + /** + * Return true if the otherBean has an Id value. + */ + public boolean hasImportedId(Object otherBean) { + + return null != targetDescriptor.getId(otherBean); + } + + public void jsonWrite(WriteJsonContext ctx, Object bean) { + + Boolean include = ctx.includeMany(name); + if (Boolean.FALSE.equals(include)){ + return; + } + + Object value = getValueIntercept(bean); + if (value != null){ + ctx.pushParentBeanMany(bean); + help.jsonWrite(ctx, name, value, include != null); + ctx.popParentBeanMany(); + } + } + + public void jsonRead(ReadJsonContext ctx, Object bean){ + + if (!ctx.readArrayBegin()) { + // the array is null + return; + } + + Object collection = help.createEmpty(false); + BeanCollectionAdd add = getBeanCollectionAdd(collection, null); + do { + ReadBeanState detailBeanState = targetDescriptor.jsonRead(ctx, name); + if (detailBeanState == null){ + // probably empty array + break; + } + Object detailBean = detailBeanState.getBean(); + add.addBean(detailBean); + + if (bean != null && childMasterProperty != null){ + // bind detail bean back to master via mappedBy property + childMasterProperty.setValue(detailBean, bean); + detailBeanState.setLoaded(childMasterProperty.getName()); + } + + detailBeanState.setLoadedState(); + + if (!ctx.readArrayNext()){ + break; + } + } while(true); + + setValue(bean, collection); + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java new file mode 100644 index 000000000..ccbd8b083 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -0,0 +1,930 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.Query; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.avaje.ebeaninternal.server.query.SqlBeanLoad; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; + +/** + * Property mapped to a joined bean. + */ +public class BeanPropertyAssocOne extends BeanPropertyAssoc { + + private final boolean oneToOne; + + private final boolean oneToOneExported; + + private final boolean embeddedVersion; + + private final boolean importedPrimaryKey; + + private final LocalHelp localHelp; + + private final BeanProperty[] embeddedProps; + + private final HashMap embeddedPropsMap; + + /** + * The information for Imported foreign Keys. + */ + private ImportedId importedId; + + private ExportedProperty[] exportedProperties; + + private String deleteByParentIdSql; + private String deleteByParentIdInSql; + BeanPropertyAssocMany relationshipProperty; + + /** + * Create based on deploy information of an EmbeddedId. + */ + public BeanPropertyAssocOne(BeanDescriptorMap owner, DeployBeanPropertyAssocOne deploy) { + this(owner, null, deploy); + } + + /** + * Create the property. + */ + public BeanPropertyAssocOne(BeanDescriptorMap owner, BeanDescriptor descriptor, + DeployBeanPropertyAssocOne deploy) { + + super(owner, descriptor, deploy); + + importedPrimaryKey = deploy.isImportedPrimaryKey(); + oneToOne = deploy.isOneToOne(); + oneToOneExported = deploy.isOneToOneExported(); + + if (embedded) { + // Overriding of the columns and use table alias of owning BeanDescriptor + BeanEmbeddedMeta overrideMeta = BeanEmbeddedMetaFactory.create(owner, deploy, descriptor); + embeddedProps = overrideMeta.getProperties(); + if (id) { + embeddedVersion = false; + } else { + embeddedVersion = overrideMeta.isEmbeddedVersion(); + } + embeddedPropsMap = new HashMap(); + for (int i = 0; i < embeddedProps.length; i++) { + embeddedPropsMap.put(embeddedProps[i].getName(), embeddedProps[i]); + } + + } else { + embeddedProps = null; + embeddedPropsMap = null; + embeddedVersion = false; + } + localHelp = createHelp(embedded, oneToOneExported); + } + + @Override + public void initialise() { + super.initialise(); + if (!isTransient) { + if (embedded) { + // no imported or exported information + } else if (!oneToOneExported) { + importedId = createImportedId(this, targetDescriptor, tableJoin); + } else { + exportedProperties = createExported(); + + String delStmt = "delete from "+targetDescriptor.getBaseTable()+" where "; + + deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false); + deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true); + + } + } + } + + public void setRelationshipProperty(BeanPropertyAssocMany relationshipProperty){ + this.relationshipProperty = relationshipProperty; + } + + public BeanPropertyAssocMany getRelationshipProperty() { + return relationshipProperty; + } + + public void cacheClear() { + if (targetDescriptor.isBeanCaching() && relationshipProperty != null) { + targetDescriptor.cacheClearCachedManyIds(relationshipProperty.getName()); + } + } + + public void cacheDelete(boolean clearOnNull, Object bean) { + if (targetDescriptor.isBeanCaching() && relationshipProperty != null) { + Object assocBean = getValue(bean); + if (assocBean != null) { + Object parentId = targetDescriptor.getId(assocBean); + if (parentId != null) { + targetDescriptor.cacheRemoveCachedManyIds(parentId, relationshipProperty.getName()); + return; + } + } + if (clearOnNull) { + targetDescriptor.cacheClearCachedManyIds(relationshipProperty.getName()); + } + } + } + + public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) { + + if (embedded){ + BeanProperty embProp = embeddedPropsMap.get(remainder); + if (embProp == null){ + String msg = "Embedded Property "+remainder+" not found in "+getFullBeanName(); + throw new PersistenceException(msg); + } + if (chain == null) { + chain = new ElPropertyChainBuilder(true, propName); + } + chain.add(this); + return chain.add(embProp).build(); + } + + return createElPropertyValue(propName, remainder, chain, propertyDeploy); + } + + @Override + public String getElPlaceholder(boolean encrypted) { + return encrypted ? elPlaceHolderEncrypted : elPlaceHolder; + } + + public SqlUpdate deleteByParentId(Object parentId, List parentIdist) { + if (parentId != null){ + return deleteByParentId(parentId); + } else { + return deleteByParentIdList(parentIdist); + } + } + + private SqlUpdate deleteByParentIdList(List parentIdist) { + + StringBuilder sb = new StringBuilder(100); + sb.append(deleteByParentIdInSql); + + String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size()); + sb.append(inClause); + + DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString()); + for (int i = 0; i < parentIdist.size(); i++) { + targetIdBinder.bindId(delete, parentIdist.get(i)); + } + + return delete; + } + + private SqlUpdate deleteByParentId(Object parentId) { + + DefaultSqlUpdate delete = new DefaultSqlUpdate(deleteByParentIdSql); + if (exportedProperties.length == 1){ + delete.addParameter(parentId); + } else { + targetDescriptor.getIdBinder().bindId(delete, parentId); + } + return delete; + } + + public List findIdsByParentId(Object parentId, List parentIdist, Transaction t) { + if (parentId != null){ + return findIdsByParentId(parentId, t); + } else { + return findIdsByParentIdList(parentIdist, t); + } + } + + private List findIdsByParentId(Object parentId, Transaction t) { + + String rawWhere = deriveWhereParentIdSql(false); + + EbeanServer server = getBeanDescriptor().getEbeanServer(); + Query q = server.find(getPropertyType()) + .where().raw(rawWhere).query(); + + bindWhereParendId(q, parentId); + return server.findIds(q, t); + } + + private List findIdsByParentIdList(List parentIdist, Transaction t) { + + String rawWhere = deriveWhereParentIdSql(true); + String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size()); + + String expr = rawWhere+inClause; + + EbeanServer server = getBeanDescriptor().getEbeanServer(); + Query q = (Query)server.find(getPropertyType()) + .where().raw(expr); + + for (int i = 0; i < parentIdist.size(); i++) { + bindWhereParendId(q, parentIdist.get(i)); + } + + return server.findIds(q, t); + } + + private void bindWhereParendId(Query q, Object parentId) { + + if (exportedProperties.length == 1) { + q.setParameter(1, parentId); + + } else { + int pos = 1; + for (int i = 0; i < exportedProperties.length; i++) { + Object embVal = exportedProperties[i].getValue(parentId); + q.setParameter(pos++, embVal); + } + } + } + + public void addFkey() { + if (importedId != null) { + importedId.addFkeys(name); + } + } + + @Override + public boolean isValueLoaded(Object value) { + if (value instanceof EntityBean) { + return ((EntityBean) value)._ebean_getIntercept().isLoaded(); + } + return true; + } + + @Override + public InvalidValue validateCascade(Object value) { + + BeanDescriptor target = getTargetDescriptor(); + return target.validate(true, value); + } + + private boolean hasChangedEmbedded(Object bean, Object oldValues) { + + Object embValue = getValue(oldValues); + if (embValue instanceof EntityBean) { + // the embedded bean .. has its own old values + return ((EntityBean) embValue)._ebean_getIntercept().isNewOrDirty(); + } + if (embValue == null) { + return getValue(bean) != null; + } else { + return false; + } + } + + @Override + public boolean hasChanged(Object bean, Object oldValues) { + if (embedded) { + return hasChangedEmbedded(bean, oldValues); + } + Object value = getValue(bean); + Object oldVal = getValue(oldValues); + if (oneToOneExported) { + // FKey on other side + return false; + } else { + if (value == null) { + return oldVal != null; + } else if (oldValues == null) { + return true; + } + + return importedId.hasChanged(value, oldVal); + } + } + + /** + * Return meta data for the deployment of the embedded bean specific to this + * property. + */ + public BeanProperty[] getProperties() { + return embeddedProps; + } + + public void buildSelectExpressionChain(String prefix, List selectChain) { + + prefix = SplitName.add(prefix, name); + + if (!embedded){ + targetIdBinder.buildSelectExpressionChain(prefix, selectChain); + + } else { + for (int i = 0; i < embeddedProps.length; i++) { + embeddedProps[i].buildSelectExpressionChain(prefix, selectChain); + } + } + } + + + /** + * Return true if this a OneToOne property. Otherwise assumed ManyToOne. + */ + public boolean isOneToOne() { + return oneToOne; + } + + /** + * Return true if this is the exported side of a OneToOne. + */ + public boolean isOneToOneExported() { + return oneToOneExported; + } + + /** + * Returns true if the associated bean has version properties. + */ + public boolean isEmbeddedVersion() { + return embeddedVersion; + } + + /** + * If true this bean maps to the primary key. + */ + public boolean isImportedPrimaryKey() { + return importedPrimaryKey; + } + + /** + * Same as getPropertyType(). Return the type of the bean this property + * represents. + */ + public Class getTargetType() { + return getPropertyType(); + } + + public Object getCacheDataValue(Object bean){ + if (embedded) { + throw new RuntimeException(); + } else { + Object ap = getValue(bean); + if (ap == null){ + return null; + } else { + return targetDescriptor.getId(ap); + } + } + } + + public void setCacheDataValue(Object bean, Object cacheData, Object oldValues, boolean readOnly){ + if (cacheData != null) { + if (embedded){ + throw new RuntimeException(); + } else { + boolean vanillaMode = false; + T ref = targetDescriptor.createReference(vanillaMode, Boolean.FALSE, cacheData, null); + setValue(bean, ref); + if (oldValues != null){ + setValue(oldValues, ref); + } + if (readOnly && !vanillaMode){ + ((EntityBean)ref)._ebean_intercept().setReadOnly(true); + } + } + } + } + + /** + * Return the Id values from the given bean. + */ + @Override + public Object[] getAssocOneIdValues(Object bean) { + return targetDescriptor.getIdBinder().getIdValues(bean); + } + + /** + * Return the Id expression to add to where clause etc. + */ + public String getAssocOneIdExpr(String prefix, String operator) { + return targetDescriptor.getIdBinder().getAssocOneIdExpr(prefix, operator); + } + + /** + * Return the logical id value expression taking into account embedded id's. + */ + @Override + public String getAssocIdInValueExpr(int size){ + return targetDescriptor.getIdBinder().getIdInValueExpr(size); + } + + /** + * Return the logical id in expression taking into account embedded id's. + */ + @Override + public String getAssocIdInExpr(String prefix){ + return targetDescriptor.getIdBinder().getAssocIdInExpr(prefix); + } + + @Override + public boolean isAssocId() { + return !embedded; + } + + @Override + public boolean isAssocProperty() { + return !embedded; + } + + + /** + * Create a vanilla bean of the target type to be used as an embeddedId + * value. + */ + public Object createEmbeddedId() { + return getTargetDescriptor().createVanillaBean(); + } + + /** + * Return an empty reference object. + */ + public Object createEmptyReference() { + return targetDescriptor.createEntityBean(); + } + + public void elSetReference(Object bean) { + Object value = getValueIntercept(bean); + if (value != null) { + ((EntityBean) value)._ebean_getIntercept().setReference(); + } + } + + @Override + public Object elGetReference(Object bean) { + Object value = getValueIntercept(bean); + if (value == null) { + value = targetDescriptor.createEntityBean(); + setValueIntercept(bean, value); + } + return value; + } + + public ImportedId getImportedId() { + return importedId; + } + + private String deriveWhereParentIdSql(boolean inClause) { + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < exportedProperties.length; i++) { + String fkColumn = exportedProperties[i].getForeignDbColumn(); + if (i > 0){ + String s = inClause ? "," : " and "; + sb.append(s); + } + sb.append(fkColumn); + if (!inClause){ + sb.append("=? "); + } + } + return sb.toString(); + } + + /** + * Create the array of ExportedProperty used to build reference objects. + */ + private ExportedProperty[] createExported() { + + BeanProperty[] uids = descriptor.propertiesId(); + + ArrayList list = new ArrayList(); + + if (uids.length == 1 && uids[0].isEmbedded()) { + + BeanPropertyAssocOne one = (BeanPropertyAssocOne) uids[0]; + BeanDescriptor targetDesc = one.getTargetDescriptor(); + BeanProperty[] emIds = targetDesc.propertiesBaseScalar(); + try { + for (int i = 0; i < emIds.length; i++) { + ExportedProperty expProp = findMatch(true, emIds[i]); + list.add(expProp); + } + } catch (PersistenceException e){ + // not found as individual scalar properties + e.printStackTrace(); + } + + } else { + for (int i = 0; i < uids.length; i++) { + ExportedProperty expProp = findMatch(false, uids[i]); + list.add(expProp); + } + } + + return (ExportedProperty[]) list.toArray(new ExportedProperty[list.size()]); + } + + /** + * Find the matching foreignDbColumn for a given local property. + */ + private ExportedProperty findMatch(boolean embeddedProp,BeanProperty prop) { + + String matchColumn = prop.getDbColumn(); + + String searchTable = tableJoin.getTable(); + TableJoinColumn[] columns = tableJoin.columns(); + + for (int i = 0; i < columns.length; i++) { + String matchTo = columns[i].getLocalDbColumn(); + + if (matchColumn.equalsIgnoreCase(matchTo)) { + String foreignCol = columns[i].getForeignDbColumn(); + return new ExportedProperty(embeddedProp, foreignCol, prop); + } + } + + String msg = "Error with the Join on ["+getFullBeanName() + +"]. Could not find the matching foreign key for ["+matchColumn+"] in table["+searchTable+"]?" + +" Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?"; + throw new PersistenceException(msg); + } + + + @Override + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + if (!isTransient) { + localHelp.appendSelect(ctx, subQuery); + } + } + + @Override + public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + if (!isTransient) { + localHelp.appendFrom(ctx, forceOuterJoin); + } + } + + @Override + public Object readSet(DbReadContext ctx, Object bean, Class type) throws SQLException { + boolean assignable = (type == null || owningType.isAssignableFrom(type)); + return localHelp.readSet(ctx, bean, assignable); + } + + /** + * Read the data from the resultSet effectively ignoring it and returning null. + */ + @Override + public Object read(DbReadContext ctx) throws SQLException { + // just read the resultSet incrementing the column index + // pass in null for the bean so any data read is ignored + return localHelp.read(ctx); + } + + @Override + public void loadIgnore(DbReadContext ctx) { + localHelp.loadIgnore(ctx); + } + + @Override + public void load(SqlBeanLoad sqlBeanLoad) throws SQLException { + Object dbVal = sqlBeanLoad.load(this); + if (embedded && sqlBeanLoad.isLazyLoad()){ + if (dbVal instanceof EntityBean){ + ((EntityBean)dbVal)._ebean_getIntercept().setLoaded(); + } + } + } + + private LocalHelp createHelp(boolean embedded, boolean oneToOneExported) { + if (embedded) { + return new Embedded(); + } else if (oneToOneExported) { + return new ReferenceExported(); + } else { + return new Reference(this); + } + } + + /** + * Local interface to handle Embedded, Reference and Reference Exported + * cases. + */ + private abstract class LocalHelp { + + abstract void loadIgnore(DbReadContext ctx); + + abstract Object read(DbReadContext ctx) throws SQLException; + + abstract Object readSet(DbReadContext ctx, Object bean, boolean assignAble) throws SQLException; + + abstract void appendSelect(DbSqlContext ctx, boolean subQuery); + + abstract void appendFrom(DbSqlContext ctx, boolean forceOuterJoin); + + } + + private final class Embedded extends LocalHelp { + + void loadIgnore(DbReadContext ctx) { + for (int i = 0; i < embeddedProps.length; i++) { + embeddedProps[i].loadIgnore(ctx); + } + } + + @Override + Object readSet(DbReadContext ctx, Object bean, boolean assignable) throws SQLException { + Object dbVal = read(ctx); + if (bean != null && assignable) { + // set back to the parent bean + setValue(bean, dbVal); + ctx.propagateState(dbVal); + return dbVal; + + } else { + return null; + } + } + + Object read(DbReadContext ctx) throws SQLException { + + EntityBean embeddedBean = targetDescriptor.createEntityBean(); + + boolean notNull = false; + for (int i = 0; i < embeddedProps.length; i++) { + Object value = embeddedProps[i].readSet(ctx, embeddedBean, null); + if (value != null) { + notNull = true; + } + } + if (notNull) { + ctx.propagateState(embeddedBean); + return embeddedBean; + } else { + return null; + } + } + + @Override + void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + } + + @Override + void appendSelect(DbSqlContext ctx, boolean subQuery) { + for (int i = 0; i < embeddedProps.length; i++) { + embeddedProps[i].appendSelect(ctx, subQuery); + } + } + } + + /** + * For imported reference - this is the common case. + */ + private final class Reference extends LocalHelp { + + //private final BeanPropertyAssocOne beanProp; + + Reference(BeanPropertyAssocOne beanProp) { +// this.beanProp = beanProp; + } + + void loadIgnore(DbReadContext ctx) { + targetIdBinder.loadIgnore(ctx); + if (targetInheritInfo != null) { + ctx.getDataReader().incrementPos(1); + } + } + + Object readSet(DbReadContext ctx, Object bean, boolean assignable) throws SQLException { + Object val = read(ctx); + if (bean != null && assignable) { + setValue(bean, val); + ctx.propagateState(val); + } + return val; + } + + /** + * Read and set a Reference bean. + */ + @Override + Object read(DbReadContext ctx) throws SQLException { + + BeanDescriptor rowDescriptor = null; + Class rowType = targetType; + if (targetInheritInfo != null) { + // read discriminator to determine the type + InheritInfo rowInheritInfo = targetInheritInfo.readType(ctx); + if (rowInheritInfo != null) { + rowType = rowInheritInfo.getType(); + rowDescriptor = rowInheritInfo.getBeanDescriptor(); + } + } + + // read the foreign key column(s) + Object id = targetIdBinder.read(ctx); + if (id == null) { + return null; + } + + // check transaction context to see if it already exists + Object existing = ctx.getPersistenceContext().get(rowType, id); + + if (existing != null) { + return existing; + } + + // parent always null for this case (but here to document) + Object parent = null; + boolean vanillaMode = ctx.isVanillaMode(); + //ReferenceOptions options = ctx.getReferenceOptionsFor(beanProp); + + Boolean readOnly = ctx.isReadOnly(); + Object ref; + if (targetInheritInfo != null) { + // for inheritance hierarchy create the correct type for this row... + ref = rowDescriptor.createReference(vanillaMode, readOnly, id, parent); + } else { + ref = targetDescriptor.createReference(vanillaMode, readOnly, id, parent); + } + + Object existingBean = ctx.getPersistenceContext().putIfAbsent(id, ref); + if (existingBean != null) { + // advanced case when we use multiple concurrent threads to + // build a single object graph, and another thread has since + // loaded a matching bean so we will use that instead. + ref = existingBean; + + } else if (!vanillaMode){ + EntityBeanIntercept ebi = ((EntityBean) ref)._ebean_getIntercept(); + if (Boolean.TRUE.equals(ctx.isReadOnly())){ + ebi.setReadOnly(true); + } + ctx.register(name, ebi); + } + + return ref; + } + + @Override + void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + if (targetInheritInfo != null) { + // add join to support the discriminator column + String relativePrefix = ctx.getRelativePrefix(name); + tableJoin.addJoin(forceOuterJoin, relativePrefix, ctx); + } + } + + /** + * Append columns for foreign key columns. + */ + @Override + void appendSelect(DbSqlContext ctx, boolean subQuery) { + + if (!subQuery && targetInheritInfo != null) { + // add discriminator column + String relativePrefix = ctx.getRelativePrefix(getName()); + String tableAlias = ctx.getTableAlias(relativePrefix); + ctx.appendColumn(tableAlias, targetInheritInfo.getDiscriminatorColumn()); + } + importedId.sqlAppend(ctx); + } + } + + /** + * For OneToOne exported reference - not so common. + */ + private final class ReferenceExported extends LocalHelp { + + @Override + void loadIgnore(DbReadContext ctx) { + targetDescriptor.getIdBinder().loadIgnore(ctx); + } + + /** + * Read and set a Reference bean. + */ + @Override + Object readSet(DbReadContext ctx, Object bean, boolean assignable) throws SQLException { + + Object dbVal = read(ctx); + if (bean != null && assignable) { + setValue(bean, dbVal); + ctx.propagateState(dbVal); + } + return dbVal; + } + + @Override + Object read(DbReadContext ctx) throws SQLException { + + // TODO: Support for Inheritance hierarchy on exported OneToOne ? + IdBinder idBinder = targetDescriptor.getIdBinder(); + Object id = idBinder.read(ctx); + if (id == null) { + return null; + } + + PersistenceContext persistCtx = ctx.getPersistenceContext(); + Object existing = persistCtx.get(targetType, id); + + if (existing != null) { + return existing; + } + boolean vanillaMode = ctx.isVanillaMode(); + Object parent = null; + Object ref = targetDescriptor.createReference(vanillaMode, ctx.isReadOnly(), id, parent); + + if (!vanillaMode){ + EntityBeanIntercept ebi = ((EntityBean) ref)._ebean_getIntercept(); + if (Boolean.TRUE.equals(ctx.isReadOnly())) { + ebi.setReadOnly(true); + } + persistCtx.put(id, ref); + ctx.register(name, ebi); + } + return ref; + } + + /** + * Append columns for foreign key columns. + */ + @Override + void appendSelect(DbSqlContext ctx, boolean subQuery) { + + // set appropriate tableAlias for + // the exported id columns + + String relativePrefix = ctx.getRelativePrefix(getName()); + ctx.pushTableAlias(relativePrefix); + + IdBinder idBinder = targetDescriptor.getIdBinder(); + idBinder.appendSelect(ctx, subQuery); + + ctx.popTableAlias(); + } + + @Override + void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + + String relativePrefix = ctx.getRelativePrefix(getName()); + tableJoin.addJoin(forceOuterJoin, relativePrefix, ctx); + } + } + + @Override + public void jsonWrite(WriteJsonContext ctx, Object bean) { + + Object value = getValueIntercept(bean); + if (value == null){ + ctx.beginAssocOneIsNull(name); + + } else { + if (ctx.isParentBean(value)){ + // bi-directional and already rendered parent + + } else { + ctx.pushParentBean(bean); + ctx.beginAssocOne(name); + BeanDescriptor refDesc = descriptor.getBeanDescriptor(value.getClass()); + refDesc.jsonWrite(ctx, value); + ctx.endAssocOne(); + ctx.popParentBean(); + } + } + } + + @Override + public void jsonRead(ReadJsonContext ctx, Object bean){ + + T assocBean = targetDescriptor.jsonReadBean(ctx, name); + setValue(bean, assocBean); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java new file mode 100644 index 000000000..5af4c324a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java @@ -0,0 +1,229 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.config.ScalarTypeConverter; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; +import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.query.SqlBeanLoad; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; +import com.avaje.ebeaninternal.server.type.CtCompoundProperty; +import com.avaje.ebeaninternal.server.type.CtCompoundPropertyElAdapter; +import com.avaje.ebeaninternal.server.type.CtCompoundType; + +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * Property mapped to an Immutable Compound Value Object. + *

+ * An Immutable Compound Value Object is similar to an Embedded bean but it + * doesn't require enhancement and MUST be treated as an Immutable type. + *

+ */ +public class BeanPropertyCompound extends BeanProperty { + + private final CtCompoundType compoundType; + + /** + * Type Converter for scala.Option and similar type wrapping. + */ + @SuppressWarnings("rawtypes") + private final ScalarTypeConverter typeConverter; + + private final BeanProperty[] scalarProperties; + + private final LinkedHashMap propertyMap = new LinkedHashMap(); + + private final LinkedHashMap nonScalarMap = new LinkedHashMap(); + + private final BeanPropertyCompoundRoot root; + + /** + * Create the property. + */ + public BeanPropertyCompound(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanPropertyCompound deploy) { + + super(owner, descriptor, deploy); + + this.compoundType = deploy.getCompoundType(); + this.typeConverter = deploy.getTypeConverter(); + + this.root = deploy.getFlatProperties(owner, descriptor); + + this.scalarProperties = root.getScalarProperties(); + + for (int i = 0; i < scalarProperties.length; i++) { + propertyMap.put(scalarProperties[i].getName(), scalarProperties[i]); + } + + List nonScalarPropsList = root.getNonScalarProperties(); + + for (int i = 0; i < nonScalarPropsList.size(); i++) { + CtCompoundProperty ctProp = nonScalarPropsList.get(i); + CtCompoundPropertyElAdapter adapter = new CtCompoundPropertyElAdapter(ctProp); + nonScalarMap.put(ctProp.getRelativeName(), adapter); + } + + } + + @Override + public void initialise() { + // do nothing for normal BeanProperty + if (!isTransient && compoundType == null) { + String msg = "No cvoInternalType assigned to " + descriptor.getFullName() + "." + getName(); + throw new RuntimeException(msg); + } + } + + @Override + public void setDeployOrder(int deployOrder) { + this.deployOrder = deployOrder; + for (CtCompoundPropertyElAdapter adapter : nonScalarMap.values()) { + adapter.setDeployOrder(deployOrder); + } + } + + /** + * Get the underlying compound type. + */ + @SuppressWarnings("unchecked") + public Object getValueUnderlying(Object bean) { + + Object value = getValue(bean); + if (typeConverter != null){ + value = typeConverter.unwrapValue(value); + } + return value; + } + + @Override + public Object getValue(Object bean) { + return super.getValue(bean); + } + + @Override + public Object getValueIntercept(Object bean) { + return super.getValueIntercept(bean); + } + + @Override + public void setValue(Object bean, Object value) { + super.setValue(bean, value); + } + + @Override + public void setValueIntercept(Object bean, Object value) { + super.setValueIntercept(bean, value); + } + + public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) { + + if (chain == null) { + chain = new ElPropertyChainBuilder(true, propName); + } + + // first add this property + chain.add(this); + + // handle all the rest of the chain handled by the + // BeanProperty (all depth for nested compound type) + BeanProperty p = propertyMap.get(remainder); + if (p != null) { + return chain.add(p).build(); + } + CtCompoundPropertyElAdapter elAdapter = nonScalarMap.get(remainder); + if (elAdapter == null) { + throw new RuntimeException("property [" + remainder + "] not found in " + getFullBeanName()); + } + return chain.add(elAdapter).build(); + } + + @Override + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + if (!isTransient) { + for (int i = 0; i < scalarProperties.length; i++) { + scalarProperties[i].appendSelect(ctx, subQuery); + } + } + } + + public BeanProperty[] getScalarProperties() { + return scalarProperties; + } + + @Override + public Object readSet(DbReadContext ctx, Object bean, Class type) throws SQLException { + + boolean assignable = (type == null || owningType.isAssignableFrom(type)); + + Object v = compoundType.read(ctx.getDataReader()); + if (assignable) { + setValue(bean, v); + } + + return v; + } + + /** + * Read the data from the resultSet effectively ignoring it and returning + * null. + */ + @SuppressWarnings("unchecked") + @Override + public Object read(DbReadContext ctx) throws SQLException { + + Object v = compoundType.read(ctx.getDataReader()); + if (typeConverter != null){ + v = typeConverter.wrapValue(v); + } + return v; + } + + @Override + public void loadIgnore(DbReadContext ctx) { + compoundType.loadIgnore(ctx.getDataReader()); + } + + @Override + public void load(SqlBeanLoad sqlBeanLoad) throws SQLException { + sqlBeanLoad.load(this); + } + + @Override + public Object elGetReference(Object bean) { + return bean; + } + + public void jsonWrite(WriteJsonContext ctx, Object bean) { + + Object valueObject = getValueIntercept(bean); + compoundType.jsonWrite(ctx, valueObject, name); + } + + public void jsonRead(ReadJsonContext ctx, Object bean){ + + Object objValue = compoundType.jsonRead(ctx); + setValue(bean, objValue); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundRoot.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundRoot.java new file mode 100644 index 000000000..7c034c4c6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundRoot.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter; +import com.avaje.ebeaninternal.server.type.CtCompoundProperty; + +/** + * Represents the root BeanProperty for properties of a compound type. + *

+ * Holds all the scalar and non-scalar properties of the compound type. The + * scalar properties match to DB columns and the non-scalar ones are here solely + * to support EL expression language for nested compound types. + *

+ * + * @author rbygrave + */ +public class BeanPropertyCompoundRoot { + + private final BeanReflectSetter setter; + + /** + * The method used to write the property. + */ + private final Method writeMethod; + + private final String name; + private final String fullBeanName; + + private final LinkedHashMap propMap; + + private final ArrayList propList; + + private List nonScalarProperties; + + public BeanPropertyCompoundRoot(DeployBeanProperty deploy) { + this.fullBeanName = deploy.getFullBeanName(); + this.name = deploy.getName(); + this.setter = deploy.getSetter(); + this.writeMethod = deploy.getWriteMethod(); + this.propList = new ArrayList(); + this.propMap = new LinkedHashMap(); + } + + public BeanProperty[] getScalarProperties() { + + return propList.toArray(new BeanProperty[propList.size()]); + } + + public void register(BeanPropertyCompoundScalar prop) { + propList.add(prop); + propMap.put(prop.getName(), prop); + } + + public BeanPropertyCompoundScalar getCompoundScalarProperty(String propName) { + return propMap.get(propName); + } + + public List getNonScalarProperties() { + return nonScalarProperties; + } + + public void setNonScalarProperties(List nonScalarProperties) { + this.nonScalarProperties = nonScalarProperties; + } + + /** + * Set the value of the property without interception or + * PropertyChangeSupport. + */ + public void setRootValue(Object bean, Object value) { + try { + if (bean instanceof EntityBean) { + setter.set(bean, value); + } else { + Object[] args = new Object[1]; + args[0] = value; + writeMethod.invoke(bean, args); + } + } catch (Exception ex) { + String beanType = bean == null ? "null" : bean.getClass().getName(); + String msg = "set " + name + " with arg[" + value + "] on ["+fullBeanName+"] with type[" + beanType + "] threw error"; + throw new RuntimeException(msg, ex); + } + } + + /** + * Set the value of the property. + */ + public void setRootValueIntercept(Object bean, Object value) { + try { + if (bean instanceof EntityBean) { + setter.setIntercept(bean, value); + } else { + Object[] args = new Object[1]; + args[0] = value; + writeMethod.invoke(bean, args); + } + } catch (Exception ex) { + String beanType = bean == null ? "null" : bean.getClass().getName(); + String msg = "setIntercept " + name + " arg[" + value + "] on ["+fullBeanName+"] with type[" + beanType + "] threw error"; + throw new RuntimeException(msg, ex); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundScalar.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundScalar.java new file mode 100644 index 000000000..383e70051 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompoundScalar.java @@ -0,0 +1,120 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.config.ScalarTypeConverter; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.type.CtCompoundProperty; + +/** + * A BeanProperty owned by a Compound value object that maps to + * a real scalar type. + * + * @author rbygrave + */ +public class BeanPropertyCompoundScalar extends BeanProperty { + + private final BeanPropertyCompoundRoot rootProperty; + + private final CtCompoundProperty ctProperty; + + @SuppressWarnings("rawtypes") + private final ScalarTypeConverter typeConverter; + + public BeanPropertyCompoundScalar(BeanPropertyCompoundRoot rootProperty, DeployBeanProperty scalarDeploy, + CtCompoundProperty ctProperty, ScalarTypeConverter typeConverter) { + + super(scalarDeploy); + this.rootProperty = rootProperty; + this.ctProperty = ctProperty; + this.typeConverter = typeConverter; + } + + @SuppressWarnings("unchecked") + @Override + public Object getValue(Object valueObject) { + if (typeConverter != null){ + valueObject = typeConverter.unwrapValue(valueObject); + } + return ctProperty.getValue(valueObject); + } + + @Override + public void setValue(Object bean, Object value) { + setValueInCompound(bean, value, false); + } + + @SuppressWarnings("unchecked") + public void setValueInCompound(Object bean, Object value, boolean intercept) { + + Object compoundValue = ctProperty.setValue(bean, value); + + if (compoundValue != null){ + if (typeConverter != null){ + compoundValue = typeConverter.wrapValue(compoundValue); + } + // we are at the top level and we have a compound value + // that we can set using the root property + if (intercept){ + rootProperty.setRootValueIntercept(bean, compoundValue); + } else { + rootProperty.setRootValue(bean, compoundValue); + } + } + } + + /** + * No interception on embedded scalar values inside a CVO. + */ + @Override + public void setValueIntercept(Object bean, Object value) { + setValueInCompound(bean, value, true); + } + + /** + * No interception on embedded scalar values inside a CVO. + */ + @Override + public Object getValueIntercept(Object bean) { + return getValue(bean); + } + + @Override + public Object elGetReference(Object bean) { + return getValue(bean); + } + + @Override + public Object elGetValue(Object bean) { + return getValue(bean); + } + + @Override + public void elSetReference(Object bean) { + super.elSetReference(bean); + } + + @Override + public void elSetValue(Object bean, Object value, boolean populate, boolean reference) { + super.elSetValue(bean, value, populate, reference); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyOverride.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyOverride.java new file mode 100644 index 000000000..5047dea69 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyOverride.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +/** + * Used hold meta data when a bean property is overridden. + *

+ * Typically this is for Embedded Beans. + *

+ */ +public class BeanPropertyOverride { + + private final String dbColumn; + + private final String sqlFormulaSelect; + + private final String sqlFormulaJoin; + + public BeanPropertyOverride(String dbColumn) { + this(dbColumn, null, null); + } + + public BeanPropertyOverride(String dbColumn, String sqlFormulaSelect, String sqlFormulaJoin) { + this.dbColumn = InternString.intern(dbColumn); + this.sqlFormulaSelect = InternString.intern(sqlFormulaSelect); + this.sqlFormulaJoin = InternString.intern(sqlFormulaJoin); + } + + public String getDbColumn() { + return dbColumn; + } + + public String getSqlFormulaSelect() { + return sqlFormulaSelect; + } + + public String getSqlFormulaJoin() { + return sqlFormulaJoin; + } + + public String replace(String src, String srcDbColumn){ + return StringHelper.replaceString(src, srcDbColumn, dbColumn); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java new file mode 100644 index 000000000..d58286599 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Iterator; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.BasicAttribute; + +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection; +import com.avaje.ebeaninternal.server.ldap.LdapPersistenceException; +import com.avaje.ebeaninternal.server.type.ScalarType; + +public class BeanPropertySimpleCollection extends BeanPropertyAssocMany { + + private final ScalarType collectionScalarType; + + public BeanPropertySimpleCollection(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanPropertySimpleCollection deploy) { + super(owner, descriptor, deploy); + this.collectionScalarType = deploy.getCollectionScalarType(); + } + + public void initialise() { + super.initialise(); + } + + @Override + public Attribute createAttribute(Object bean) { + Object v = getValue(bean); + if (v == null){ + return null; + } + if (ldapAttributeAdapter != null){ + return ldapAttributeAdapter.createAttribute(v); + } + + BasicAttribute attrs = new BasicAttribute(getDbColumn()); + + Iterator it = help.getIterator(v); + if (it != null){ + while (it.hasNext()) { + Object beanValue = it.next(); + Object attrValue = collectionScalarType.toJdbcType(beanValue); + attrs.add(attrValue); + } + } + return attrs; + } + + @Override + public void setAttributeValue(Object bean, Attribute attr) { + try { + if (attr != null){ + Object beanValue; + if (ldapAttributeAdapter != null){ + beanValue = ldapAttributeAdapter.readAttribute(attr); + + } else { + boolean vanilla = true; + beanValue = help.createEmpty(vanilla); + BeanCollectionAdd collAdd = help.getBeanCollectionAdd(beanValue, mapKey); + + NamingEnumeration en = attr.getAll(); + while (en.hasMoreElements()) { + Object attrValue = (Object) en.nextElement(); + Object collValue = collectionScalarType.toBeanType(attrValue); + collAdd.addBean(collValue); + } + } + + setValue(bean, beanValue); + } + } catch (NamingException e) { + throw new LdapPersistenceException(e); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanQueryAdapterManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanQueryAdapterManager.java new file mode 100644 index 000000000..940a248c6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanQueryAdapterManager.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.List; +import java.util.logging.Logger; + +import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; + +/** + * Default implementation for creating BeanControllers. + */ +public class BeanQueryAdapterManager { + + private static final Logger logger = Logger.getLogger(BeanQueryAdapterManager.class.getName()); + + private final List list; + + public BeanQueryAdapterManager(BootupClasses bootupClasses){ + + list = bootupClasses.getBeanQueryAdapters(); + } + + public int getRegisterCount() { + return list.size(); + } + + /** + * Return the BeanPersistController for a given entity type. + */ + public void addQueryAdapter(DeployBeanDescriptor deployDesc){ + + for (int i = 0; i < list.size(); i++) { + BeanQueryAdapter c = list.get(i); + if (c.isRegisterFor(deployDesc.getBeanType())){ + logger.fine("BeanQueryAdapter on[" + deployDesc.getFullName() + "] " + c.getClass().getName()); + deployDesc.addQueryAdapter(c); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java new file mode 100644 index 000000000..18a4680c8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java @@ -0,0 +1,174 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.InvalidValue; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.common.BeanSet; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; + +/** + * Helper specifically for dealing with Sets. + */ +public final class BeanSetHelp implements BeanCollectionHelp { + + private final BeanPropertyAssocMany many; + private final BeanDescriptor targetDescriptor; + private BeanCollectionLoader loader; + + /** + * When attached to a specific many property. + */ + public BeanSetHelp(BeanPropertyAssocMany many){ + this.many = many; + this.targetDescriptor = many.getTargetDescriptor(); + } + + /** + * For a query that returns a set. + */ + public BeanSetHelp(){ + this.many = null; + this.targetDescriptor = null; + } + + public void setLoader(BeanCollectionLoader loader){ + this.loader = loader; + } + + public Iterator getIterator(Object collection) { + return ((Set) collection).iterator(); + } + + public BeanCollectionAdd getBeanCollectionAdd(Object bc,String mapKey) { + if (bc instanceof BeanSet){ + BeanSet beanSet = (BeanSet)bc; + if (beanSet.getActualSet() == null){ + beanSet.setActualSet(new LinkedHashSet()); + } + return beanSet; + } else if (bc instanceof Set) { + return new VanillaAdd((Set)bc); + + } else { + throw new RuntimeException("Unhandled type "+bc); + } + } + + + @SuppressWarnings("unchecked") + static class VanillaAdd implements BeanCollectionAdd { + + @SuppressWarnings("rawtypes") + private final Set set; + + private VanillaAdd(Set set) { + this.set = set; + } + + public void addBean(Object bean) { + set.add(bean); + } + } + + public void add(BeanCollection collection, Object bean) { + collection.internalAdd(bean); + } + + public Object createEmpty(boolean vanilla) { + return vanilla ? new LinkedHashSet() : new BeanSet(); + } + + public BeanCollection createReference(Object parentBean, String propertyName) { + + return new BeanSet(loader, parentBean, propertyName); + } + + public ArrayList validate(Object manyValue) { + + ArrayList errs = null; + + Set set = (Set)manyValue; + Iterator i = set.iterator(); + while (i.hasNext()) { + Object detailBean = i.next(); + InvalidValue invalid = targetDescriptor.validate(true, detailBean); + if (invalid != null){ + if (errs == null){ + errs = new ArrayList(); + } + errs.add(invalid); + } + } + return errs; + } + + public void refresh(EbeanServer server, Query query, Transaction t, Object parentBean) { + + BeanSet newBeanSet = (BeanSet)server.findSet(query, t); + refresh(newBeanSet, parentBean); + } + + public void refresh(BeanCollection bc, Object parentBean) { + + BeanSet newBeanSet = (BeanSet)bc; + + Set current = (Set)many.getValueUnderlying(parentBean); + + newBeanSet.setModifyListening(many.getModifyListenMode()); + if (current == null){ + // the currentList is null? Not really expecting this... + many.setValue(parentBean,newBeanSet); + + } else if (current instanceof BeanSet) { + // normally this case, replace just the underlying list + BeanSet currentBeanSet = (BeanSet)current; + currentBeanSet.setActualSet(newBeanSet.getActualSet()); + currentBeanSet.setModifyListening(many.getModifyListenMode()); + + } else { + // replace the entire set + many.setValue(parentBean, newBeanSet); + } + } + + public void jsonWrite(WriteJsonContext ctx, String name, Object collection, boolean explicitInclude) { + + Set set; + if (collection instanceof BeanCollection){ + BeanSet bc = (BeanSet)collection; + if (!bc.isPopulated()){ + if (explicitInclude){ + // invoke lazy loading as collection + // is explicitly included in the output + bc.size(); + } else { + return; + } + } + set = bc.getActualSet(); + } else { + set = (Set)collection; + } + + int count = 0; + ctx.beginAssocMany(name); + Iterator it = set.iterator(); + while (it.hasNext()) { + Object detailBean = it.next(); + if (count++ > 0){ + ctx.appendComma(); + } + targetDescriptor.jsonWrite(ctx, detailBean); + } + ctx.endAssocMany(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelect.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelect.java new file mode 100644 index 000000000..c37b8cfb1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelect.java @@ -0,0 +1,88 @@ +package com.avaje.ebeaninternal.server.deploy; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +public class BeanSqlSelect { + + public static final String HAVING_PREDICATES = "${HAVING_PREDICATES}"; + + public static final String WHERE_PREDICATES = "${WHERE_PREDICATES}"; + + public static final String AND_PREDICATES = "${AND_PREDICATES}"; + + public static final String ORDER_BY = "${ORDER_BY}"; + + public enum PredicatesType { + HAVING, WHERE, AND, NONE + } + + final String sql; + + final PredicatesType predicatesType; + + final boolean hasOrderBy; + + public BeanSqlSelect(String sql, PredicatesType predicatesType, boolean hasOrderBy) { + this.sql = sql; + this.predicatesType = predicatesType; + this.hasOrderBy = hasOrderBy; + } + + public String getSql() { + return sql; + } + + public PredicatesType getPredicatesType() { + return predicatesType; + } + + public boolean hasOrderBy() { + return hasOrderBy; + } + + public String addPredicates(String query, String predicates) { + if (predicates == null) { + switch (predicatesType) { + case HAVING: + return StringHelper.replaceString(query, HAVING_PREDICATES, ""); + case WHERE: + return StringHelper.replaceString(query, WHERE_PREDICATES, ""); + case AND: + return StringHelper.replaceString(query, AND_PREDICATES, ""); + case NONE: + return query; + + default: + throw new PersistenceException("predicatesType " + predicatesType + " not handled"); + } + } else { + switch (predicatesType) { + case HAVING: + return StringHelper + .replaceString(query, HAVING_PREDICATES, " HAVING " + predicates); + case WHERE: + return StringHelper.replaceString(query, WHERE_PREDICATES, " WHERE " + predicates); + case AND: + return StringHelper.replaceString(query, AND_PREDICATES, " AND " + predicates); + case NONE: + return query; + + default: + throw new PersistenceException("predicatesType " + predicatesType + " not handled"); + } + } + } + + public String addOrderBy(String query, String orderBy) { + if (!hasOrderBy) { + return query; + } + if (orderBy == null) { + return StringHelper.replaceString(query, ORDER_BY, ""); + } else { + return StringHelper.replaceString(query, ORDER_BY, " ORDER BY " + orderBy); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelectFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelectFactory.java new file mode 100644 index 000000000..b58fc9498 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSqlSelectFactory.java @@ -0,0 +1,59 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.deploy.BeanSqlSelect.PredicatesType; + +public class BeanSqlSelectFactory { + + public static BeanSqlSelect create(String sql){ + + sql = trim(sql); + + PredicatesType predicatesType = determinePredicatesType(sql); + boolean hasOrderBy = determineHasOrderBy(sql); + + return new BeanSqlSelect(sql, predicatesType, hasOrderBy); + } + + private static boolean determineHasOrderBy(String sql){ + return sql.indexOf(BeanSqlSelect.ORDER_BY) > 0; + } + + private static PredicatesType determinePredicatesType(String sql){ + if (sql.indexOf(BeanSqlSelect.HAVING_PREDICATES) > 0){ + return PredicatesType.HAVING; + } + if (sql.indexOf(BeanSqlSelect.WHERE_PREDICATES) > 0){ + return PredicatesType.WHERE; + } + if (sql.indexOf(BeanSqlSelect.AND_PREDICATES) > 0){ + return PredicatesType.AND; + } + return PredicatesType.NONE; + } + + private static String trim(String sql) { + + boolean removeWhitespace = false; + + int length = sql.length(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + char c = sql.charAt(i); + if (removeWhitespace){ + if (!Character.isWhitespace(c)){ + sb.append(c); + removeWhitespace = false; + } + } else { + if (c == '\r' || c == '\n'){ + sb.append('\n'); + removeWhitespace = true; + } else { + sb.append(c); + } + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java new file mode 100644 index 000000000..efbf171f4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanTable.java @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanTable; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; + + +/** + * Used for associated beans in place of a BeanDescriptor. This is done to avoid + * recursion issues due to the potentially bi-directional and circular + * relationships between beans. + *

+ * It holds the main deployment information and not all the detail that is held + * in a BeanDescriptor. + *

+ */ +public class BeanTable { + + private static final Logger logger = Logger.getLogger(BeanTable.class.getName()); + + private final Class beanType; + + /** + * The base table. + */ + private final String baseTable; + + private final BeanProperty[] idProperties; + + /** + * Create the BeanTable. + */ + public BeanTable(DeployBeanTable mutable, BeanDescriptorMap owner) { + this.beanType = mutable.getBeanType(); + this.baseTable = InternString.intern(mutable.getBaseTable()); + this.idProperties = mutable.createIdProperties(owner); + } + + public String toString(){ + return baseTable; + } + + /** + * Return the base table for this BeanTable. + * This is used to determine the join information + * for associations. + */ + public String getBaseTable() { + return baseTable; + } + + /** + * Gets the unqualified base table. + * + * @return the unqualified base table + */ + public String getUnqualifiedBaseTable(){ + final String[] chunks = baseTable.split("\\."); + return chunks.length == 2 ? chunks[1] :chunks[0]; + } + + /** + * Return the Id properties. + */ + public BeanProperty[] getIdProperties() { + return idProperties; + } + + /** + * Return the class for this beanTable. + */ + public Class getBeanType() { + return beanType; + } + + public void createJoinColumn(String foreignKeyPrefix, DeployTableJoin join, boolean reverse) { + + boolean complexKey = false; + BeanProperty[] props = idProperties; + + if (idProperties.length == 1){ + if (idProperties[0] instanceof BeanPropertyAssocOne) { + BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne)idProperties[0]; + props = assocOne.getProperties(); + complexKey = true; + } + } + + for (int i = 0; i < props.length; i++) { + + String lc = props[i].getDbColumn(); + String fk = lc; + if (foreignKeyPrefix != null){ + fk = foreignKeyPrefix+"_"+fk; + } + + if (complexKey){ + // check to see if we want prefixes by default with complex keys + boolean usePrefixOnComplex = GlobalProperties.getBoolean("ebean.prefixComplexKeys", false); + if (!usePrefixOnComplex){ + // just to copy the column name rather than prefix with the foreignKeyPrefix. + // I think that with complex keys this is the more common approach. + String msg = "On table["+baseTable+"] foreign key column ["+lc+"]"; + logger.log(Level.FINE, msg); + fk = lc; + } + } + + DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk); + if (reverse){ + joinCol = joinCol.reverse(); + } + join.addJoinColumn(joinCol); + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java new file mode 100644 index 000000000..ac389efd1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java @@ -0,0 +1,155 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebean.event.BeanPersistRequest; + +public class ChainedBeanPersistController implements BeanPersistController { + + private static final Sorter SORTER = new Sorter(); + + private final List list; + private final BeanPersistController[] chain; + + /** + * Construct adding 2 BeanPersistController's. + */ + public ChainedBeanPersistController(BeanPersistController c1, BeanPersistController c2) { + this(addList(c1, c2)); + } + + /** + * Helper method used to create a list from 2 BeanPersistController's. + */ + private static List addList(BeanPersistController c1, BeanPersistController c2) { + ArrayList addList = new ArrayList(2); + addList.add(c1); + addList.add(c2); + return addList; + } + + /** + * Construct given the list of BeanPersistController's. + * @param list + */ + public ChainedBeanPersistController(List list) { + this.list = list; + BeanPersistController[] c = list.toArray(new BeanPersistController[list.size()]); + Arrays.sort(c, SORTER); + this.chain = c; + } + + /** + * Register a new BeanPersistController and return the resulting chain. + */ + public ChainedBeanPersistController register(BeanPersistController c) { + if (list.contains(c)){ + return this; + } else { + ArrayList newList = new ArrayList(); + newList.addAll(list); + newList.add(c); + + return new ChainedBeanPersistController(newList); + } + } + + /** + * De-register a BeanPersistController and return the resulting chain. + */ + public ChainedBeanPersistController deregister(BeanPersistController c) { + if (!list.contains(c)){ + return this; + } else { + ArrayList newList = new ArrayList(); + newList.addAll(list); + newList.remove(c); + + return new ChainedBeanPersistController(newList); + } + } + + /** + * Always returns 0 (not used for this object). + */ + public int getExecutionOrder() { + return 0; + } + + /** + * Always returns false (not used for this object). + */ + public boolean isRegisterFor(Class cls) { + return false; + } + + public void postDelete(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + chain[i].postDelete(request); + } + } + + public void postInsert(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + chain[i].postInsert(request); + } + } + + public void postLoad(Object bean, Set includedProperties) { + for (int i = 0; i < chain.length; i++) { + chain[i].postLoad(bean, includedProperties); + } + } + + public void postUpdate(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + chain[i].postUpdate(request); + } + } + + public boolean preDelete(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + if (!chain[i].preDelete(request)) { + return false; + } + } + return true; + } + + public boolean preInsert(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + if (!chain[i].preInsert(request)) { + return false; + } + } + return true; + } + + public boolean preUpdate(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + if (!chain[i].preUpdate(request)) { + return false; + } + } + return true; + } + + /** + * Helper to order the BeanPersistController's in a chain. + */ + private static class Sorter implements Comparator { + + public int compare(BeanPersistController o1, BeanPersistController o2) { + + int i1 = o1.getExecutionOrder() ; + int i2 = o2.getExecutionOrder() ; + return (i1 implements BeanPersistListener { + + private final List> list; + private final BeanPersistListener[] chain; + + /** + * Construct adding 2 BeanPersistController's. + */ + public ChainedBeanPersistListener(BeanPersistListener c1, BeanPersistListener c2) { + this(addList(c1, c2)); + } + + /** + * Helper method used to create a list from 2 BeanPersistListener. + */ + private static List> addList(BeanPersistListener c1, BeanPersistListener c2) { + ArrayList> addList = new ArrayList>(2); + addList.add(c1); + addList.add(c2); + return addList; + } + + /** + * Construct given the list of BeanPersistController's. + * @param list + */ + @SuppressWarnings("unchecked") + public ChainedBeanPersistListener(List> list) { + this.list = list; + this.chain = list.toArray(new BeanPersistListener[list.size()]); + } + + /** + * Register a new BeanPersistController and return the resulting chain. + */ + public ChainedBeanPersistListener register(BeanPersistListener c) { + if (list.contains(c)){ + return this; + } else { + List> newList = new ArrayList>(); + newList.addAll(list); + newList.add(c); + + return new ChainedBeanPersistListener(newList); + } + } + + /** + * De-register a BeanPersistController and return the resulting chain. + */ + public ChainedBeanPersistListener deregister(BeanPersistListener c) { + if (!list.contains(c)){ + return this; + } else { + ArrayList> newList = new ArrayList>(); + newList.addAll(list); + newList.remove(c); + + return new ChainedBeanPersistListener(newList); + } + } + + + public boolean deleted(T bean) { + boolean notifyCluster = false; + for (int i = 0; i < chain.length; i++) { + if (chain[i].deleted(bean)) { + notifyCluster = true; + } + } + return notifyCluster; + } + + public boolean inserted(T bean) { + boolean notifyCluster = false; + for (int i = 0; i < chain.length; i++) { + if (chain[i].inserted(bean)) { + notifyCluster = true; + } + } + return notifyCluster; + } + + public void remoteDelete(Object id) { + for (int i = 0; i < chain.length; i++) { + chain[i].remoteDelete(id); + } + } + + public void remoteInsert(Object id) { + for (int i = 0; i < chain.length; i++) { + chain[i].remoteInsert(id); + } + } + + public void remoteUpdate(Object id) { + for (int i = 0; i < chain.length; i++) { + chain[i].remoteUpdate(id); + } + } + + public boolean updated(T bean, Set updatedProperties) { + boolean notifyCluster = false; + for (int i = 0; i < chain.length; i++) { + if (chain[i].updated(bean, updatedProperties)) { + notifyCluster = true; + } + } + return notifyCluster; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanQueryAdapter.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanQueryAdapter.java new file mode 100644 index 000000000..f241efc65 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanQueryAdapter.java @@ -0,0 +1,113 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.event.BeanQueryRequest; + +/** + * Handles multiple BeanQueryAdapter for a given entity type. + */ +public class ChainedBeanQueryAdapter implements BeanQueryAdapter { + + private static final Sorter SORTER = new Sorter(); + + private final List list; + private final BeanQueryAdapter[] chain; + + /** + * Construct adding 2 BeanQueryAdapter's. + */ + public ChainedBeanQueryAdapter(BeanQueryAdapter c1, BeanQueryAdapter c2) { + this(addList(c1, c2)); + } + + /** + * Helper method used to create a list from 2 BeanQueryAdapter. + */ + private static List addList(BeanQueryAdapter c1, BeanQueryAdapter c2) { + ArrayList addList = new ArrayList(2); + addList.add(c1); + addList.add(c2); + return addList; + } + + /** + * Construct given the list of BeanQueryAdapter's. + * @param list + */ + public ChainedBeanQueryAdapter(List list) { + this.list = list; + BeanQueryAdapter[] c = list.toArray(new BeanQueryAdapter[list.size()]); + Arrays.sort(c, SORTER); + this.chain = c; + } + + /** + * Register a new BeanQueryAdapter and return the resulting chain. + */ + public ChainedBeanQueryAdapter register(BeanQueryAdapter c) { + if (list.contains(c)){ + return this; + } else { + List newList = new ArrayList(); + newList.addAll(list); + newList.add(c); + + return new ChainedBeanQueryAdapter(newList); + } + } + + /** + * De-register a BeanQueryAdapter and return the resulting chain. + */ + public ChainedBeanQueryAdapter deregister(BeanQueryAdapter c) { + if (!list.contains(c)){ + return this; + } else { + ArrayList newList = new ArrayList(); + newList.addAll(list); + newList.remove(c); + + return new ChainedBeanQueryAdapter(newList); + } + } + + + /** + * Return 0 as not used by this Chained adapter. + */ + public int getExecutionOrder() { + return 0; + } + + /** + * Return false as only individual adapters are registered. + */ + public boolean isRegisterFor(Class cls) { + return false; + } + + public void preQuery(BeanQueryRequest request) { + + for (int i = 0; i < chain.length; i++) { + chain[i].preQuery(request); + } + } + /** + * Helper to order the BeanQueryAdapter's in a chain. + */ + private static class Sorter implements Comparator { + + public int compare(BeanQueryAdapter o1, BeanQueryAdapter o2) { + + int i1 = o1.getExecutionOrder() ; + int i2 = o2.getExecutionOrder() ; + return (i1 + * This typically means wrap and unwrap mutable scala collection types of Buffer, Set and Map. + *

+ * + * @author rbygrave + * + */ +public interface CollectionTypeConverter { + + /** + * Convert the wrapped type to the underlying Java List, Set or Map. + */ + public Object toUnderlying(Object wrapped); + + /** + * Wrap the underlying Java List, Set or Map into the final collection type. + */ + public Object toWrapped(Object wrapped); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/CompoundUniqueContraint.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/CompoundUniqueContraint.java new file mode 100644 index 000000000..df9c819f9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/CompoundUniqueContraint.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +/** + * Holds multiple column unique constraints defined for an entity. + */ +public class CompoundUniqueContraint { + + private final String[] columns; + + public CompoundUniqueContraint(String[] columns) { + this.columns = columns; + } + + /** + * Return the columns that make up this unique constraint. + */ + public String[] getColumns() { + return columns; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/CopyContext.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/CopyContext.java new file mode 100644 index 000000000..d4176fbbe --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/CopyContext.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; + +/** + * Provides context when performing a bean copy. + * + * @author rbygrave + */ +public class CopyContext { + + private final boolean vanillaMode; + + private final boolean sharing; + + private final PersistenceContext pc; + + public CopyContext(boolean vanillaMode, boolean sharing) { + this.vanillaMode = vanillaMode; + this.sharing = sharing; + this.pc = new DefaultPersistenceContext(); + } + + public CopyContext(boolean vanillaMode) { + this(vanillaMode, false); + } + + /** + * Return true if the copy should be a vanilla bean. + */ + public boolean isVanillaMode() { + return vanillaMode; + } + + /** + * Return true if the copy should be safe for sharing. + */ + public boolean isSharing() { + return sharing; + } + + /** + * Return the persistence context used during the copy. + */ + public PersistenceContext getPersistenceContext() { + return pc; + } + + /** + * Put the bean if absent into the persistence context. + */ + public Object putIfAbsent(Object id, Object bean){ + return pc.putIfAbsent(id, bean); + } + + /** + * Return the bean for the given type and id from the persistence context. + */ + public Object get(Class beanType, Object beanId){ + return pc.get(beanType, beanId); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DNativeQuery.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DNativeQuery.java new file mode 100644 index 000000000..82f00799b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DNativeQuery.java @@ -0,0 +1,18 @@ +package com.avaje.ebeaninternal.server.deploy; + +/** + * A native query defined in deployment xml. + */ +public class DNativeQuery { + + final String query; + + public DNativeQuery(String query) { + this.query = query; + } + + public String getQuery() { + return query; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlColumnInfo.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlColumnInfo.java new file mode 100644 index 000000000..6af03fe74 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlColumnInfo.java @@ -0,0 +1,42 @@ +/** + * + */ +package com.avaje.ebeaninternal.server.deploy; + +public class DRawSqlColumnInfo { + + final String name; + + final String label; + + final String propertyName; + + final boolean scalarProperty; + + public DRawSqlColumnInfo(String name, String label, String propertyName, boolean scalarProperty) { + this.name = name; + this.label = label; + this.propertyName = propertyName; + this.scalarProperty = scalarProperty; + } + + public String getName() { + return name; + } + + public String getLabel() { + return label; + } + + public String getPropertyName() { + return propertyName; + } + + public boolean isScalarProperty() { + return scalarProperty; + } + + public String toString() { + return "name:" + name + " label:" + label + " prop:" + propertyName; + } + } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlMeta.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlMeta.java new file mode 100644 index 000000000..47a8820cb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlMeta.java @@ -0,0 +1,139 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.annotation.SqlSelect; + +/** + * Meta data for a sql-select object. + *

+ * Created from SqlSelect annotation or xml deployment. + *

+ */ +public class DRawSqlMeta { + + private String name; + private String tableAlias; + private String extend; + private String query; + private boolean debug; + private String where; + private String having; + private String columnMapping; + + + public DRawSqlMeta(SqlSelect sqlSelect) { + this.debug = sqlSelect.debug(); + this.name = sqlSelect.name(); + this.tableAlias = toNull(sqlSelect.tableAlias()); + this.extend = toNull(sqlSelect.extend()); + this.having = toNull(sqlSelect.having()); + this.where = toNull(sqlSelect.where()); + this.columnMapping = toNull(sqlSelect.columnMapping()); + this.query = toNull(sqlSelect.query()); + } + + public DRawSqlMeta(String name, String extend, String query, boolean debug, + String where, String having, String columnMapping) { + + this.name = name; + this.extend = extend; + this.query = query; + this.debug = debug; + this.having = having; + this.where = where; + this.columnMapping = columnMapping; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void setTableAlias(String tableAlias) { + this.tableAlias = tableAlias; + } + + public String getTableAlias() { + return tableAlias; + } + + public String getExtend() { + return extend; + } + + public void setExtend(String extend) { + this.extend = extend; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + + public String getWhere() { + return where; + } + + public void setWhere(String where) { + this.where = where; + } + + public String getHaving() { + return having; + } + + public void setHaving(String having) { + this.having = having; + } + + public String getColumnMapping() { + return columnMapping; + } + + public void setColumnMapping(String columnMapping) { + this.columnMapping = columnMapping; + } + + public void extend(DRawSqlMeta parentQuery){ + extendQuery(parentQuery.getQuery()); + extendColumnMapping(parentQuery.getColumnMapping()); + } + + /** + * Prepend sql from the parent query that this query 'extends'. + */ + private void extendQuery(String parentSql) { + if (query == null) { + query = parentSql; + } else { + query = parentSql + " " + query; + } + } + + private void extendColumnMapping(String parentColumnMapping) { + if (columnMapping == null){ + columnMapping = parentColumnMapping; + } + } + + private static String toNull(String s) { + if (s != null && s.equals("")){ + return null; + } else { + return s; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelect.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelect.java new file mode 100644 index 000000000..4e10dc04e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelect.java @@ -0,0 +1,281 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.query.CQueryPredicates; +import com.avaje.ebeaninternal.server.query.SqlTree; +import com.avaje.ebeaninternal.server.query.SqlTreeNode; +import com.avaje.ebeaninternal.server.query.SqlTreeNodeRoot; +import com.avaje.ebeaninternal.server.query.SqlTreeProperties; + +/** + * Represents a SqlSelect raw sql query. + */ +public class DRawSqlSelect { + + private static final Logger logger = Logger.getLogger(DRawSqlSelect.class.getName()); + + private final BeanDescriptor desc; + + private final DRawSqlColumnInfo[] selectColumns; + + private final Map columnMap; + + private final String preWhereExprSql; + + private final boolean andWhereExpr; + + private final String preHavingExprSql; + + private final boolean andHavingExpr; + + private final String orderBySql; + + private final String whereClause; + + private final String havingClause; + + private final String query; + + private final String columnMapping; + + private final String name; + + private final SqlTree sqlTree; + + private boolean withId; + + private final String tableAlias; + + public DRawSqlSelect(BeanDescriptor desc, List selectColumns, + String tableAlias, String preWhereExprSql, boolean andWhereExpr, String preHavingExprSql, + boolean andHavingExpr, String orderBySql, DRawSqlMeta meta) { + + this.desc = desc; + this.tableAlias = tableAlias; + this.selectColumns = selectColumns.toArray(new DRawSqlColumnInfo[selectColumns.size()]); + this.preHavingExprSql = preHavingExprSql; + this.preWhereExprSql = preWhereExprSql; + this.andHavingExpr = andHavingExpr; + this.andWhereExpr = andWhereExpr; + this.orderBySql = orderBySql; + this.name = meta.getName(); + this.whereClause = meta.getWhere(); + this.havingClause = meta.getHaving(); + this.query = meta.getQuery(); + this.columnMapping = meta.getColumnMapping(); + + this.sqlTree = initialise(desc); + this.columnMap = createColumnMap(this.selectColumns); + } + + private Map createColumnMap(DRawSqlColumnInfo[] selectColumns) { + + HashMap m = new HashMap(); + for (int i = 0; i < selectColumns.length; i++) { + m.put(selectColumns[i].getPropertyName(), selectColumns[i]); + } + + return m; + } + + /** + * Find foreign keys for assoc one types and build SqlTree. + */ + private SqlTree initialise(BeanDescriptor owner){ + + try { + return buildSqlTree(owner); + + } catch (Exception e){ + String m = "Bug? initialising query "+name+" on "+owner; + throw new RuntimeException(m, e); + } + } + + /** + * Return the RawSqlColumnInfo given it's logical property name. + */ + public DRawSqlColumnInfo getRawSqlColumnInfo(String propertyName){ + return columnMap.get(propertyName); + } + + public String getTableAlias() { + return tableAlias; + } + + /** + * Build the SqlTree for this query. + *

+ * Most commonly this is just a simple list of properties - aka flat, but it + * could be a real object graph tree for more complex scenarios. + *

+ */ + private SqlTree buildSqlTree(BeanDescriptor desc){ + + + SqlTree sqlTree = new SqlTree(); + sqlTree.setSummary(desc.getName()); + + LinkedHashSet includedProps = new LinkedHashSet(); + SqlTreeProperties selectProps = new SqlTreeProperties(); + + for (int i = 0; i < selectColumns.length; i++) { + + DRawSqlColumnInfo columnInfo = selectColumns[i]; + String propName = columnInfo.getPropertyName(); + BeanProperty beanProperty = desc.getBeanProperty(propName); + if (beanProperty != null) { + if (beanProperty.isId()){ + if (i > 0){ + String m = "With "+desc+" query:"+name+" the ID is not the first column in the select. It must be..."; + throw new PersistenceException(m); + } else { + withId = true; + } + } else { + includedProps.add(beanProperty.getName()); + selectProps.add(beanProperty); + } + + + } else { + String m = "Mapping for " + desc.getFullName(); + m += " query["+name+"] column[" + columnInfo + "] index[" + i; + m += "] not matched to bean property?"; + logger.log(Level.SEVERE, m); + } + } + + selectProps.setIncludedProperties(includedProps); + SqlTreeNode sqlRoot = new SqlTreeNodeRoot(desc, selectProps, null, withId); + sqlTree.setRootNode(sqlRoot); + + return sqlTree; + } + + /** + * Build the full SQL Select statement for the request. + */ + public String buildSql(String orderBy, CQueryPredicates predicates, OrmQueryRequest request) { + + + StringBuilder sb = new StringBuilder(); + sb.append(preWhereExprSql); + sb.append(" "); + + String dynamicWhere = null; + if (request.getQuery().getId() != null) { + // need to convert this as well. This avoids the + // assumption that id has its proper dbColumn assigned + // which may change if using multiple raw sql statements + // against the same bean. + BeanDescriptor descriptor = request.getBeanDescriptor(); + //FIXME: I think this is broken... needs to be logical + // and then parsed for RawSqlSelect... + dynamicWhere = descriptor.getIdBinderIdSql(); + } + + String dbWhere = predicates.getDbWhere(); + if (dbWhere != null && dbWhere.length() > 0) { + if (dynamicWhere == null) { + dynamicWhere = dbWhere; + } else { + dynamicWhere += " and " + dbWhere; + } + } + + if (dynamicWhere != null) { + if (andWhereExpr) { + sb.append(" and "); + } else { + sb.append(" where "); + } + sb.append(dynamicWhere); + sb.append(" "); + } + + if (preHavingExprSql != null) { + sb.append(preHavingExprSql); + sb.append(" "); + } + + String dbHaving = predicates.getDbHaving(); + + if (dbHaving != null && dbHaving.length() > 0) { + if (andHavingExpr) { + sb.append(" and "); + } else { + sb.append(" having "); + } + sb.append(dbHaving); + sb.append(" "); + } + + if (orderBy != null) { + sb.append(" order by ").append(orderBy); + } + + return sb.toString(); + } + + public String getOrderBy(CQueryPredicates predicates) { + String orderBy = predicates.getDbOrderBy(); + if (orderBy != null) { + return orderBy; + } else { + return orderBySql; + } + } + + + public String getName() { + return name; + } + + public SqlTree getSqlTree() { + return sqlTree; + } + + public boolean isWithId() { + return withId; + } + + public String getQuery() { + return query; + } + + public String getColumnMapping() { + return columnMapping; + } + + public String getWhereClause() { + return whereClause; + } + + public String getHavingClause() { + return havingClause; + } + + public String toString() { + return Arrays.toString(selectColumns); + } + + public BeanDescriptor getBeanDescriptor() { + return desc; + } + + public DeployParser createDeployPropertyParser() { + return new DeployPropertyParserRawSql(this); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectBuilder.java new file mode 100644 index 000000000..a541d85e7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectBuilder.java @@ -0,0 +1,355 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.List; + +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebeaninternal.server.querydefn.SimpleTextParser; + +/** + * Parses sql-select queries to try and determine the location where WHERE and HAVING + * clauses can be added dynamically to the sql. + */ +public class DRawSqlSelectBuilder { + + public static final String $_AND_HAVING = "${andHaving}"; + + public static final String $_HAVING = "${having}"; + + public static final String $_AND_WHERE = "${andWhere}"; + + public static final String $_WHERE = "${where}"; + + private static final String ORDER_BY = "order by"; + + private final BeanDescriptor desc; + + private final NamingConvention namingConvention; + + private final DRawSqlMeta meta; + + private final boolean debug; + + private String sql; + + private final SimpleTextParser textParser; + + private List selectColumns; + + private int placeHolderWhere; + private int placeHolderAndWhere; + private int placeHolderHaving; + private int placeHolderAndHaving; + private boolean hasPlaceHolders; + + private int selectPos = -1; + private int fromPos = -1; + private int wherePos = -1; + private int groupByPos = -1; + private int havingPos = -1; + private int orderByPos = -1; + + private boolean whereExprAnd; + private int whereExprPos = -1; + private boolean havingExprAnd; + private int havingExprPos = -1; + + private String tableAlias; + + public DRawSqlSelectBuilder(NamingConvention namingConvention, BeanDescriptor desc, DRawSqlMeta sqlSelectMeta) { + + this.namingConvention = namingConvention; + this.desc = desc; + this.tableAlias = sqlSelectMeta.getTableAlias(); + this.meta = sqlSelectMeta; + this.debug = sqlSelectMeta.isDebug(); + this.sql = sqlSelectMeta.getQuery().trim(); + this.hasPlaceHolders = findAndRemovePlaceHolders(); + this.textParser = new SimpleTextParser(this.sql); + } + + protected NamingConvention getNamingConvention() { + return namingConvention; + } + + protected BeanDescriptor getBeanDescriptor() { + return desc; + } + + protected boolean isDebug() { + return debug; + } + + protected void debug(String msg) { + if (debug) { + System.out.println("debug> " + msg); + } + } + + public DeployNamedQuery parse() { + + if (debug) { + debug(""); + debug("Parsing sql-select in " + getErrName()); + } + + if (hasPlaceHolders()) { + + } else { + // parse the sql for the keywords... + // select, from, where, having, group by, order by + parseSqlFindKeywords(true); + } + + selectColumns = findSelectColumns(meta.getColumnMapping()); + whereExprPos = findWhereExprPosition(); + havingExprPos = findHavingExprPosition(); + + String preWhereExprSql = removeWhitespace(findPreWhereExprSql()); + String preHavingExprSql = removeWhitespace(findPreHavingExprSql()); + + preWhereExprSql = trimSelectKeyword(preWhereExprSql); + + String orderBySql = findOrderBySql(); + + DRawSqlSelect rawSqlSelect = new DRawSqlSelect(desc, selectColumns, tableAlias, preWhereExprSql, + whereExprAnd, preHavingExprSql, havingExprAnd, orderBySql, meta); + + return new DeployNamedQuery(rawSqlSelect); + } + + /** + * Find and remove the known place holders such as ${where}. + */ + private boolean findAndRemovePlaceHolders() { + placeHolderWhere = removePlaceHolder($_WHERE); + placeHolderAndWhere = removePlaceHolder($_AND_WHERE); + placeHolderHaving = removePlaceHolder($_HAVING); + placeHolderAndHaving = removePlaceHolder($_AND_HAVING); + return hasPlaceHolders(); + } + + private int removePlaceHolder(String placeHolder) { + int pos = sql.indexOf(placeHolder); + if (pos > -1) { + int after = pos + placeHolder.length() + 1; + if (after > sql.length()) { + sql = sql.substring(0, pos); + } else { + sql = sql.substring(0, pos) + sql.substring(after); + } + } + return pos; + } + + private boolean hasPlaceHolders() { + if (placeHolderWhere > -1) { + return true; + } + if (placeHolderAndWhere > -1) { + return true; + } + if (placeHolderHaving > -1) { + return true; + } + if (placeHolderAndHaving > -1) { + return true; + } + return false; + } + + /** + * Trim off the select keyword (to support row_number() limit function). + */ + private String trimSelectKeyword(String preWhereExprSql) { + + if (preWhereExprSql.length() < 7){ + throw new RuntimeException("Expecting at least 7 chars in ["+preWhereExprSql+"]"); + } + + String select = preWhereExprSql.substring(0, 7); + if (!select.equalsIgnoreCase("select ")){ + throw new RuntimeException("Expecting ["+preWhereExprSql+"] to start with \"select\""); + } + return preWhereExprSql.substring(7); + } + + + private String findOrderBySql() { + if (orderByPos > -1) { + int pos = orderByPos + ORDER_BY.length(); + return sql.substring(pos); + } + return null; + } + + private String findPreHavingExprSql() { + if (havingExprPos > whereExprPos) { + // an order by clause follows... + return sql.substring(whereExprPos, havingExprPos - 1); + } + if (whereExprPos > -1) { + // the rest of the sql... + return sql.substring(whereExprPos); + } + return null; + } + + private String findPreWhereExprSql() { + if (whereExprPos > -1) { + return sql.substring(0, whereExprPos - 1); + } else { + return sql; + } + } + + protected String getErrName() { + return "entity[" + desc.getFullName() + "] query[" + meta.getName() + "]"; + } + + /** + * Find the columns in the select clause including the table alias' and + * column alias. + */ + private List findSelectColumns(String selectClause) { + + if (selectClause == null || selectClause.trim().length() == 0) { + if (hasPlaceHolders) { + if (debug) { + debug("... No explicit ColumnMapping, so parse the sql looking for SELECT and FROM keywords."); + } + parseSqlFindKeywords(false); + } + if (selectPos == -1 || fromPos == -1) { + String msg = "Error in [" + getErrName() + "] parsing sql looking "; + msg += "for SELECT and FROM keywords."; + msg += " select:" + selectPos + " from:" + fromPos; + msg += ". You could use an explicit columnMapping to bypass this error."; + throw new RuntimeException(msg); + } + selectPos += "select".length(); + selectClause = sql.substring(selectPos, fromPos); + } + + selectClause = selectClause.trim(); + if (debug) { + debug("ColumnMapping ... [" + selectClause + "]"); + } + + return new DRawSqlSelectColumnsParser(this,selectClause).parse(); + } + + private void parseSqlFindKeywords(boolean allKeywords) { + + debug("Parsing query looking for SELECT..."); + selectPos = textParser.findWordLower("select"); + if (selectPos == -1) { + String msg = "Error in "+getErrName()+" parsing sql, can not find SELECT keyword in:"; + throw new RuntimeException(msg + sql); + } + debug("Parsing query looking for FROM... SELECT found at " + selectPos); + fromPos = textParser.findWordLower("from"); + if (fromPos == -1) { + String msg = "Error in "+getErrName()+" parsing sql, can not find FROM keyword in:"; + throw new RuntimeException(msg + sql); + } + + if (!allKeywords) { + return; + } + + debug("Parsing query looking for WHERE... FROM found at " + fromPos); + wherePos = textParser.findWordLower("where"); + if (wherePos == -1) { + debug("Parsing query looking for GROUP... no WHERE found"); + groupByPos = textParser.findWordLower("group", fromPos + 5); + } else { + debug("Parsing query looking for GROUP... WHERE found at " + wherePos); + groupByPos = textParser.findWordLower("group"); + } + if (groupByPos > -1) { + debug("Parsing query looking for HAVING... GROUP found at " + groupByPos); + havingPos = textParser.findWordLower("having"); + } + + int startOrderBy = havingPos; + if (startOrderBy == -1) { + startOrderBy = groupByPos; + } + if (startOrderBy == -1) { + startOrderBy = wherePos; + } + if (startOrderBy == -1) { + startOrderBy = fromPos; + } + + debug("Parsing query looking for ORDER... starting at " + startOrderBy); + orderByPos = textParser.findWordLower("order", startOrderBy); + } + + private int findWhereExprPosition() { + if (hasPlaceHolders) { + if (placeHolderWhere > -1) { + return placeHolderWhere; + } else { + whereExprAnd = true; + return placeHolderAndWhere; + } + } + whereExprAnd = wherePos > 0; + if (groupByPos > 0) { + return groupByPos; + } + if (havingPos > 0) { + return havingPos; + } + if (orderByPos > 0) { + return orderByPos; + } + return -1; + } + + private int findHavingExprPosition() { + if (hasPlaceHolders) { + if (placeHolderHaving > -1) { + return placeHolderHaving; + } else { + havingExprAnd = true; + return placeHolderAndHaving; + } + } + havingExprAnd = havingPos > 0; + if (orderByPos > 0) { + return orderByPos; + } + return -1; + } + + private String removeWhitespace(String sql) { + if (sql == null) { + return ""; + } + + boolean removeWhitespace = false; + + int length = sql.length(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + char c = sql.charAt(i); + if (removeWhitespace) { + if (!Character.isWhitespace(c)) { + sb.append(c); + removeWhitespace = false; + } + } else { + if (c == '\r' || c == '\n') { + sb.append('\n'); + removeWhitespace = true; + } else { + sb.append(c); + } + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectColumnsParser.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectColumnsParser.java new file mode 100644 index 000000000..db95dbeb3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DRawSqlSelectColumnsParser.java @@ -0,0 +1,238 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.config.NamingConvention; + +/** + * Parses columnMapping (select clause) mapping columns to bean properties. + */ +public final class DRawSqlSelectColumnsParser { + + private static Logger logger = Logger.getLogger(DRawSqlSelectColumnsParser.class.getName()); + + /** + * Description of how the match was made. + */ + private String matchDescription; + + /** + * The actual column string used in search for a matching property. + *

+ * This has table alias' and quoted identifiers removed. + *

+ */ + private String searchColumn; + + private int columnIndex; + + private int pos; + + private final int end; + + private final String sqlSelect; + + private final List columns = new ArrayList(); + + private final BeanDescriptor desc; + + private final NamingConvention namingConvention; + + private final DRawSqlSelectBuilder parent; + + private final boolean debug; + + public DRawSqlSelectColumnsParser(DRawSqlSelectBuilder parent, String sqlSelect) { + this.parent = parent; + this.debug = parent.isDebug(); + this.namingConvention = parent.getNamingConvention(); + this.desc = parent.getBeanDescriptor(); + this.sqlSelect = sqlSelect; + this.end = sqlSelect.length(); + } + + public List parse() { + while (pos <= end) { + nextColumnInfo(); + } + return columns; + } + + private void nextColumnInfo() { + int start = pos; + nextComma(); + String colInfo = sqlSelect.substring(start, pos); + pos++; + colInfo = colInfo.trim(); + int secLastSpace = -1; + int lastSpace = colInfo.lastIndexOf(' '); + if (lastSpace > -1) { + secLastSpace = colInfo.lastIndexOf(' ', lastSpace - 1); + } + String colName = null; + String colLabel = null; + if (lastSpace == -1) { + // no column alias + colName = colInfo; + colLabel = colName; + } else if (secLastSpace == -1) { + // no 'as' keyword + colName = colInfo.substring(0, lastSpace); + colLabel = colInfo.substring(lastSpace + 1); + if (colName.equals("")) { + colName = colLabel; + } + } else { + // check for as keyword + String expectedAs = colInfo.substring(secLastSpace + 1, lastSpace); + if (expectedAs.toLowerCase().equals("as")) { + colName = colInfo.substring(0, secLastSpace); + colLabel = colInfo.substring(lastSpace + 1); + } else { + String msg = "Error in " + parent.getErrName() + ". "; + msg += "Expected \"AS\" keyword but got [" + expectedAs + "] in select clause [" + + colInfo + "]"; + throw new PersistenceException(msg); + } + } + + + BeanProperty prop = findProperty(colLabel); + if (prop == null) { + if (debug) { + String msg = "ColumnMapping ... idx[" + columnIndex + + "] ERROR, no property found to match... column[" + colName + "] label[" + colLabel + + "] search[" + searchColumn + "]"; + parent.debug(msg); + } + String msg = "Error in " + parent.getErrName() + ". "; + msg += "No matching bean property for column[" + colName + "] columnLabel[" + colLabel + + "] idx[" + columnIndex + "] using search[" + searchColumn + "] found?"; + logger.log(Level.SEVERE, msg); + + } else { + + + String msg = null; + if (debug || logger.isLoggable(Level.FINE)) { + msg = "ColumnMapping ... idx[" + columnIndex + "] match column[" + colName + + "] label[" + colLabel + "] to property[" + prop + "]" + + matchDescription; + } + if (debug) { + parent.debug(msg); + } + if (logger.isLoggable(Level.FINE)) { + logger.fine(msg); + } + + DRawSqlColumnInfo info = new DRawSqlColumnInfo(colName, colLabel, prop.getName(), prop.isScalar()); + columns.add(info); + columnIndex++; + + } + } + + private String removeQuotedIdentifierChars(String columnLabel) { + + char c = columnLabel.charAt(0); + if (Character.isJavaIdentifierStart(c)) { + return columnLabel; + } + + // trim off first and last character + String result = columnLabel.substring(1, columnLabel.length() - 1); + + String msg = "sql-select trimming quoted identifier from[" + + columnLabel + "] to[" + result+ "]"; + logger.fine(msg); + + return result; + } + + /** + * Find the property to match against the given resultSet column. + */ + private BeanProperty findProperty(String column) { + + searchColumn = column; + int dotPos = searchColumn.indexOf("."); + if (dotPos > -1) { + searchColumn = searchColumn.substring(dotPos + 1); + } + + searchColumn = removeQuotedIdentifierChars(searchColumn); + + BeanProperty matchingProp = desc.getBeanProperty(searchColumn); + if (matchingProp != null) { + matchDescription = ""; + return matchingProp; + } + + // convert columnName using the namingConvention + String propertyName = namingConvention.getPropertyFromColumn(desc.getBeanType(), searchColumn); + matchingProp = desc.getBeanProperty(propertyName); + if (matchingProp != null) { + matchDescription = " ... using naming convention"; + return matchingProp; + } + + matchDescription = " ... by linear search"; + + // search all properties matching against the property db column + BeanProperty[] propertiesBase = desc.propertiesBaseScalar(); + for (int i = 0; i < propertiesBase.length; i++) { + BeanProperty prop = propertiesBase[i]; + if (isMatch(prop, searchColumn)) { + return prop; + } + } + + BeanProperty[] propertiesId = desc.propertiesId(); + for (int i = 0; i < propertiesId.length; i++) { + BeanProperty prop = propertiesId[i]; + if (isMatch(prop, searchColumn)) { + return prop; + } + } + + BeanPropertyAssocOne[] propertiesAssocOne = desc.propertiesOne(); + for (int i = 0; i < propertiesAssocOne.length; i++) { + BeanProperty prop = propertiesAssocOne[i]; + if (isMatch(prop, searchColumn)) { + return prop; + } + } + + return null; + } + + private boolean isMatch(BeanProperty prop, String columnLabel) { + if (columnLabel.equalsIgnoreCase(prop.getDbColumn())) { + return true; + } + if (columnLabel.equalsIgnoreCase(prop.getName())) { + return true; + } + return false; + } + + private int nextComma() { + boolean inQuote = false; + while (pos < end) { + char c = sqlSelect.charAt(pos); + if (c == '\'') { + inQuote = !inQuote; + } else if (!inQuote && c == ',') { + return pos; + } + pos++; + } + return pos; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java new file mode 100644 index 000000000..965e168a6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java @@ -0,0 +1,96 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Map; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.type.DataReader; + +/** + * Context provided when a BeanProperty reads from a ResultSet. + */ +public interface DbReadContext { + + /** + * Return the state of the object graph. + */ + public Boolean isReadOnly(); + + /** + * Propagate the state to the bean. + */ + public void propagateState(Object e); + + /** + * Return the DataReader. + */ + public DataReader getDataReader(); + + /** + * Return true if vanilla objects should be returned. + */ + public boolean isVanillaMode(); + + /** + * Return true if the query is using supplied SQL rather than generated SQL. + */ + public boolean isRawSql(); + +// /** +// * Return the reference options for a given bean property. +// */ +// public ReferenceOptions getReferenceOptionsFor(BeanPropertyAssocOne beanProperty); + + /** + * Set the JoinNode - used by proxy/reference beans for profiling. + */ + public void setCurrentPrefix(String currentPrefix, Map pathMap); + + /** + * Return true if we are profiling this query. + */ + public boolean isAutoFetchProfiling(); + + /** + * Add autoFetch profiling for a loaded entity bean. + */ + public void profileBean(EntityBeanIntercept ebi, String prefix); + + /** + * Return the persistence context. + */ + public PersistenceContext getPersistenceContext(); + + /** + * Register a reference for lazy loading. + */ + public void register(String path, EntityBeanIntercept ebi); + + /** + * Register a collection for lazy loading. + */ + public void register(String path, BeanCollection bc); + + /** + * Return the property that is associated with the many. There can only be + * one. This can be null. + */ + public BeanPropertyAssocMany getManyProperty(); + + /** + * Set back the bean that has just been loaded with its id. + */ + public void setLoadedBean(Object loadedBean, Object id); + + /** + * Set back the 'detail' bean that has just been loaded. + */ + public void setLoadedManyBean(Object loadedBean); + + /** + * Return the query mode. + */ + public SpiQuery.Mode getQueryMode(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java new file mode 100644 index 000000000..d78aef344 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java @@ -0,0 +1,116 @@ +package com.avaje.ebeaninternal.server.deploy; + + +/** + * Used to provide context during sql construction. + */ +public interface DbSqlContext { + + /** + * Add a join to the sql query. + */ + public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2); + + public void pushSecondaryTableAlias(String alias); + + /** + * Push the current table alias onto the stack. + */ + public void pushTableAlias(String tableAlias); + + /** + * Pop the current table alias from the stack. + */ + public void popTableAlias(); + + /** + * Add an encrypted property which will require additional binding. + */ + public void addEncryptedProp(BeanProperty prop); + + /** + * Return a list of encrypted properties which require additional binding. + */ + public BeanProperty[] getEncryptedProps(); + + /** + * Append a char directly to the SQL buffer. + */ + public DbSqlContext append(char s); + + /** + * Append a string directly to the SQL buffer. + */ + public DbSqlContext append(String s); + + /** + * Peek the current table alias. + */ + public String peekTableAlias(); + + /** + * Add a raw column to the sql. + */ + public void appendRawColumn(String rawcolumnWithTableAlias); + + /** + * Append a column with an explicit table alias. + */ + public void appendColumn(String tableAlias, String column); + + /** + * Append a column with the current table alias. + */ + public void appendColumn(String column); + + /** + * Append a Sql Formula select. This converts the "${ta}" keyword to the + * current table alias. + */ + public void appendFormulaSelect(String sqlFormulaSelect); + + /** + * Append a Sql Formula join. This converts the "${ta}" keyword to the + * current table alias. + */ + public void appendFormulaJoin(String sqlFormulaJoin, boolean forceOuterJoin); + + /** + * Return the current content length. + */ + public int length(); + + /** + * Return the current context of the sql context. + */ + public String getContent(); + + /** + * Return the current join node. + */ + public String peekJoin(); + + /** + * Push a join node onto the stack. + */ + public void pushJoin(String prefix); + + /** + * Pop a join node off the stack. + */ + public void popJoin(); + + /** + * Return a table alias without many where clause joins. + * Typically this is for the select clause (fetch joins). + */ + public String getTableAlias(String prefix); + + /** + * Return a table alias that takes into account many where joins. + */ + public String getTableAliasManyWhere(String prefix); + + public String getRelativePrefix(String propName); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DefaultBeanFinderManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DefaultBeanFinderManager.java new file mode 100644 index 000000000..525a42d46 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DefaultBeanFinderManager.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.HashMap; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.event.BeanFinder; + +/** + * Default implementation for BeanFinderFactory. + */ +public class DefaultBeanFinderManager implements BeanFinderManager { + + HashMap, BeanFinder> registerFor = new HashMap, BeanFinder>(); + + public int createBeanFinders(List> finderClassList) { + + for (Class cls : finderClassList) { + Class entityType = getEntityClass(cls); + try { + BeanFinder beanFinder = (BeanFinder) cls.newInstance(); + registerFor.put(entityType, beanFinder); + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + return registerFor.size(); + } + + public int getRegisterCount() { + return registerFor.size(); + } + + /** + * Return the BeanFinder for a given entity type. + */ + @SuppressWarnings("unchecked") + public BeanFinder getBeanFinder(Class entityType) { + return (BeanFinder)registerFor.get(entityType); + } + + /** + * Find the entity class given the controller class. + *

+ * This uses reflection to find the generics parameter type. + *

+ */ + private Class getEntityClass(Class controller){ + + Class cls = ParamTypeUtil.findParamType(controller, BeanFinder.class); + + if (cls == null){ + String msg = "Could not determine the entity class (generics parameter type) from "+controller+" using reflection."; + throw new PersistenceException(msg); + } + return cls; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedQuery.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedQuery.java new file mode 100644 index 000000000..2486d6de9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedQuery.java @@ -0,0 +1,80 @@ +package com.avaje.ebeaninternal.server.deploy; + +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; + +import com.avaje.ebean.RawSql; + +public class DeployNamedQuery { + + private final String name; + + private final String query; + + private final QueryHint[] hints; + + private final DRawSqlSelect sqlSelect; + + private final RawSql rawSql; + + public DeployNamedQuery(NamedQuery namedQuery) { + this.name = namedQuery.name(); + this.query = namedQuery.query(); + this.hints = namedQuery.hints(); + this.sqlSelect = null; + this.rawSql = null; + } + + public DeployNamedQuery(String name, String query, QueryHint[] hints) { + this.name = name; + this.query = query; + this.hints = hints; + this.sqlSelect = null; + this.rawSql = null; + } + + public DeployNamedQuery(String name, RawSql rawSql) { + this.name = name; + this.query = null; + this.hints = null; + this.sqlSelect = null; + this.rawSql = rawSql; + } + + public DeployNamedQuery(DRawSqlSelect sqlSelect) { + this.name = sqlSelect.getName(); + this.query = null; + this.hints = null; + this.sqlSelect = sqlSelect; + this.rawSql = null; + } + + public boolean isRawSql() { + return rawSql != null; + } + + public boolean isSqlSelect() { + return sqlSelect != null; + } + + public String getName() { + return name; + } + + public String getQuery() { + return query; + } + + public QueryHint[] getHints() { + return hints; + } + + public RawSql getRawSql() { + return rawSql; + } + + public DRawSqlSelect getSqlSelect() { + return sqlSelect; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedUpdate.java new file mode 100644 index 000000000..f5d762745 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployNamedUpdate.java @@ -0,0 +1,40 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.annotation.NamedUpdate; + +/** + * Deployment information for a named update. + */ +public class DeployNamedUpdate { + + private final String name; + + private final String updateStatement; + + private final boolean notifyCache; + + private String sqlUpdateStatement; + + public DeployNamedUpdate(NamedUpdate update) { + this.name = update.name(); + this.updateStatement = update.update(); + this.notifyCache = update.notifyCache(); + } + + public void initialise(DeployUpdateParser parser) { + sqlUpdateStatement = parser.parse(updateStatement); + } + + public String getName() { + return name; + } + + public String getSqlUpdateStatement() { + return sqlUpdateStatement; + } + + public boolean isNotifyCache() { + return notifyCache; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java new file mode 100644 index 000000000..4f2917554 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployOrmXml.java @@ -0,0 +1,191 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.lib.resource.ResourceContent; +import com.avaje.ebeaninternal.server.lib.resource.ResourceSource; +import com.avaje.ebeaninternal.server.lib.util.Dnode; +import com.avaje.ebeaninternal.server.lib.util.DnodeReader; + +/** + * Controls the creation and caching of BeanManager's, BeanDescriptors, + * BeanTable etc for both beans and tables(MapBeans). + *

+ * Also supports some other deployment features such as type conversion. + *

+ */ +public class DeployOrmXml { + + private static final Logger logger = Logger.getLogger(DeployOrmXml.class.getName()); + + + private final HashMap nativeQueryCache; + + private final ArrayList ormXmlList; + + private final ResourceSource resSource; + + public DeployOrmXml(ResourceSource resSource) { + + this.resSource = resSource; + this.nativeQueryCache = new HashMap(); + this.ormXmlList = findAllOrmXml(); + + + initialiseNativeQueries(); + } + + /** + * Register all the native queries in ALL orm xml deployment. + */ + private void initialiseNativeQueries() { + for (Dnode ormXml : ormXmlList) { + initialiseNativeQueries(ormXml); + } + } + + /** + * Register the native queries in this particular orm xml deployment. + */ + private void initialiseNativeQueries(Dnode ormXml) { + + Dnode entityMappings = ormXml.find("entity-mappings"); + if (entityMappings != null) { + List nq = entityMappings.findAll("named-native-query", 1); + for (int i = 0; i < nq.size(); i++) { + Dnode nqNode = nq.get(i); + Dnode nqQueryNode = nqNode.find("query"); + if (nqQueryNode != null) { + String queryContent = nqQueryNode.getNodeContent(); + String queryName = (String) nqNode.getAttribute("name"); + + if (queryName != null && queryContent != null) { + DNativeQuery query = new DNativeQuery(queryContent); + nativeQueryCache.put(queryName, query); + } + } + } + } + } + + /** + * Return a native named query. + *

+ * These are loaded from the orm.xml deployment file. + *

+ */ + public DNativeQuery getNativeQuery(String name) { + return nativeQueryCache.get(name); + } + + private ArrayList findAllOrmXml() { + + ArrayList ormXmlList = new ArrayList(); + + + String defaultFile = "orm.xml"; + readOrmXml(defaultFile, ormXmlList); + + if (!ormXmlList.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (Dnode ox : ormXmlList) { + sb.append(", ").append(ox.getAttribute("ebean.filename")); + } + String loadedFiles = sb.toString().substring(2); + logger.info("Deployment xml [" + loadedFiles + "] loaded."); + } + + return ormXmlList; + } + + private boolean readOrmXml(String ormXmlName, ArrayList ormXmlList) { + + try { + Dnode ormXml = null; + ResourceContent content = resSource.getContent(ormXmlName); + if (content != null) { + // servlet resource or file system... + ormXml = readOrmXml(content.getInputStream()); + + } else { + // try the classpath... + ormXml = readOrmXmlFromClasspath(ormXmlName); + } + + if (ormXml != null) { + ormXml.setAttribute("ebean.filename", ormXmlName); + ormXmlList.add(ormXml); + return true; + + } else { + return false; + } + } catch (IOException e) { + logger.log(Level.SEVERE, "error reading orm xml deployment " + ormXmlName, e); + return false; + } + } + + private Dnode readOrmXmlFromClasspath(String ormXmlName) throws IOException { + InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(ormXmlName); + if (is == null) { + return null; + } else { + return readOrmXml(is); + } + } + + private Dnode readOrmXml(InputStream in) throws IOException { + DnodeReader reader = new DnodeReader(); + Dnode ormXml = reader.parseXml(in); + in.close(); + return ormXml; + } + + /** + * Find the deployment xml for a given entity. This will return null if no + * matching deployment xml is found for this entity. + *

+ * This searches all the ormXml files and returns the first match. + *

+ */ + public Dnode findEntityDeploymentXml(String className) { + + for (Dnode ormXml : ormXmlList) { + Dnode entityMappings = ormXml.find("entity-mappings"); + + List entities = entityMappings.findAll("entity", "class", className, 1); + if (entities.size() == 1) { + return entities.get(0); + } + } + + return null; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java new file mode 100644 index 000000000..0101c0087 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java @@ -0,0 +1,183 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Set; + +/** + * Converts logical property names to database columns. + */ +public abstract class DeployParser { + + /** + * used to identify sql literal. + */ + protected static final char SINGLE_QUOTE = '\''; + + /** + * used to identify query named parameters. + */ + protected static final char COLON = ':'; + + /** + * Used to determine when a column name terminates. + */ + protected static final char UNDERSCORE = '_'; + + /** + * Used to determine when a column name terminates. + */ + protected static final char PERIOD = '.'; + + protected boolean encrypted; + + protected String source; + + protected StringBuilder sb; + + protected int sourceLength; + + protected int pos; + + protected String word; + + protected char wordTerminator; + + protected abstract String convertWord(); + + public abstract String getDeployWord(String expression); + + + /** + * Return the join includes. + */ + public abstract Set getIncludes(); + + public void setEncrypted(boolean encrytped) { + this.encrypted = encrytped; + } + + public String parse(String source) { + + if (source == null) { + return source; + } + + pos = -1; + this.source = source; + this.sourceLength = source.length(); + this.sb = new StringBuilder(source.length() + 20); + + while (nextWord()) { + String deployWord = convertWord(); + sb.append(deployWord); + if (pos < sourceLength) { + sb.append(wordTerminator); + } + } + + return sb.toString(); + } + + private boolean nextWord() { + + if (!findWordStart()) { + return false; + } + + StringBuilder wordBuffer = new StringBuilder(); + wordBuffer.append(source.charAt(pos)); + while (++pos < sourceLength) { + char ch = source.charAt(pos); + if (isWordPart(ch)) { + wordBuffer.append(ch); + } else { + wordTerminator = ch; + break; + } + } + + word = wordBuffer.toString(); + + return true; + } + + private boolean findWordStart() { + while (++pos < sourceLength) { + char ch = source.charAt(pos); + if (ch == SINGLE_QUOTE) { + // read a literal value and just + // append to string builder + sb.append(ch); + readLiteral(); + } else if (ch == COLON) { + // read a named parameter + // just append to string builder + sb.append(ch); + readNamedParameter(); + } else if (isWordStart(ch)) { + // its the start of a word that could + // be translated + return true; + } else { + sb.append(ch); + } + } + return false; + } + + /** + * Read the rest of a literal value. These do not get translated so are just + * read and appended to the string builder. + */ + private void readLiteral() { + while (++pos < sourceLength) { + char ch = source.charAt(pos); + sb.append(ch); + if (ch == SINGLE_QUOTE) { + break; + } + } + } + + /** + * Read a named parameter. These are not translated. They will be replaced + * by positioned parameters later. + */ + private void readNamedParameter() { + while (++pos < sourceLength) { + char ch = source.charAt(pos); + sb.append(ch); + if (Character.isWhitespace(ch)) { + break; + } else if (ch == ',') { + break; + } + } + } + + /** + * return true if the char is a letter, digit or underscore. + */ + private boolean isWordPart(char ch) { + if (Character.isLetterOrDigit(ch)) { + return true; + + } else if (ch == UNDERSCORE) { + return true; + + } else if (ch == PERIOD) { + return true; + + } else { + return false; + } + } + + private boolean isWordStart(char ch) { + if (Character.isLetter(ch)) { + return true; + + } else { + return false; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParser.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParser.java new file mode 100644 index 000000000..6b5b603d5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParser.java @@ -0,0 +1,52 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.HashSet; +import java.util.Set; + +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; + +/** + * Converts logical property names to database columns with table alias. + *

+ * In doing so it builds an 'includes' set which becomes the joins required to + * support the properties parsed. + *

+ */ +public final class DeployPropertyParser extends DeployParser { + + + private final BeanDescriptor beanDescriptor; + + private final Set includes = new HashSet(); + + public DeployPropertyParser(BeanDescriptor beanDescriptor) { + this.beanDescriptor = beanDescriptor; + } + + public Set getIncludes() { + return includes; + } + + @Override + public String getDeployWord(String expression) { + ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(expression); + if (elProp == null){ + return null; + } else { + addIncludes(elProp.getElPrefix()); + return elProp.getElPlaceholder(encrypted); + } + } + + @Override + public String convertWord() { + String r = getDeployWord(word); + return r == null ? word : r; + } + + private void addIncludes(String prefix) { + if (prefix != null){ + includes.add(prefix); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMap.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMap.java new file mode 100644 index 000000000..13f41684c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMap.java @@ -0,0 +1,40 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Map; +import java.util.Set; + +/** + * Converts logical property names to database columns using a Map. + */ +public final class DeployPropertyParserMap extends DeployParser { + + private final Map map; + + public DeployPropertyParserMap(Map map) { + this.map = map; + } + + /** + * Returns null for raw sql queries. + */ + public Set getIncludes() { + return null; + } + + public String convertWord() { + String r = getDeployWord(word); + return r == null ? word : r; + } + + @Override + public String getDeployWord(String expression) { + + String deployExpr = map.get(expression); + if (deployExpr == null) { + return null; + } else { + return deployExpr; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserRawSql.java new file mode 100644 index 000000000..23746e15c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserRawSql.java @@ -0,0 +1,41 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Set; + +/** + * Converts logical property names to database columns for the raw sql. + *

+ * This is used in building the where and having clauses for SqlSelect queries. + *

+ */ +public final class DeployPropertyParserRawSql extends DeployParser { + + private final DRawSqlSelect rawSqlSelect; + + public DeployPropertyParserRawSql(DRawSqlSelect rawSqlSelect) { + this.rawSqlSelect = rawSqlSelect; + } + + /** + * Returns null for raw sql queries. + */ + public Set getIncludes() { + return null; + } + + public String convertWord() { + String r = getDeployWord(word); + return r == null ? word : r; + } + + @Override + public String getDeployWord(String expression) { + DRawSqlColumnInfo columnInfo = rawSqlSelect.getRawSqlColumnInfo(expression); + if (columnInfo == null) { + return null; + } else { + return columnInfo.getName(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateMapFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateMapFactory.java new file mode 100644 index 000000000..8a67ff6be --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateMapFactory.java @@ -0,0 +1,57 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; + +/** + * Build a map of logical to physical names for use in Orm Updates. + * + */ +public class DeployUpdateMapFactory { + + private static final Logger logger = Logger.getLogger(DeployUpdateMapFactory.class.getName()); + + /** + * Build a map of logical to physical names for use in Orm Updates for a given descriptor. + *

+ * This includes the dbWrite scalar properties and imported foreign key properties. + *

+ */ + public static Map build(BeanDescriptor descriptor) { + + Map deployMap = new HashMap(); + + String shortName = descriptor.getName(); + String beanName = shortName.toLowerCase(); + deployMap.put(beanName, descriptor.getBaseTable()); + + BeanProperty[] baseScalar = descriptor.propertiesBaseScalar(); + for (BeanProperty baseProp : baseScalar) { + // excluding formula, secondary table properties + if (baseProp.isDbInsertable() || baseProp.isDbUpdatable()){ + deployMap.put(baseProp.getName().toLowerCase(), baseProp.getDbColumn()); + } + } + + BeanPropertyAssocOne[] oneImported = descriptor.propertiesOneImported(); + for (BeanPropertyAssocOne assocOne : oneImported) { + + ImportedId importedId = assocOne.getImportedId(); + if (importedId == null){ + String m = descriptor.getFullName()+" importedId is null for associated: "+assocOne.getFullBeanName(); + logger.log(Level.SEVERE, m); + + } else if (importedId.isScalar()){ + deployMap.put(importedId.getLogicalName(), importedId.getDbColumn()); + } + } + + return deployMap; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateParser.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateParser.java new file mode 100644 index 000000000..cf6261b3d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployUpdateParser.java @@ -0,0 +1,90 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.Set; + +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; + +/** + * For updates converts logical property names to database columns and bean type to base table. + */ +public final class DeployUpdateParser extends DeployParser { + + private final BeanDescriptor beanDescriptor; + + public DeployUpdateParser(BeanDescriptor beanDescriptor) { + this.beanDescriptor = beanDescriptor; + } + + /** + * Return null as not used for updates. + */ + @Override + public Set getIncludes() { + return null; + } + + public String convertWord() { + + String dbWord = getDeployWord(word); + + if (dbWord != null) { + return dbWord; + } + // maybe tableAlias.propertyName + return convertSubword(0, word, null); + } + + private String convertSubword(int start, String currentWord, StringBuilder localBuffer) { + + int dotPos = currentWord.indexOf('.', start); + if (start == 0 && dotPos == -1){ + return currentWord; + } + if (start == 0){ + localBuffer = new StringBuilder(); + } + if (dotPos == -1){ + // no match... + localBuffer.append(currentWord.substring(start)); + return localBuffer.toString(); + } + + // append up to the dot + localBuffer.append(currentWord.substring(start, dotPos+1)); + + if (dotPos == currentWord.length()-1){ + // ends with a "." ??? + return localBuffer.toString(); + } + + // get the remainder after the dot + start = dotPos+1; + String remainder = currentWord.substring(start, currentWord.length()); + + //String dbWord = deployMap.get(remainder.toLowerCase()); + String dbWord = getDeployWord(remainder); + if (dbWord != null){ + // we have found a match for the remainder + localBuffer.append(dbWord); + return localBuffer.toString(); + } else { + // + return convertSubword(start, currentWord, localBuffer); + } + } + + public String getDeployWord(String expression) { + + if (expression.equalsIgnoreCase(beanDescriptor.getName())){ + return beanDescriptor.getBaseTable(); + } + + ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(expression); + if (elProp != null){ + return elProp.getDbColumn(); + } else { + return null; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DetermineManyType.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DetermineManyType.java new file mode 100644 index 000000000..7042abf6b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DetermineManyType.java @@ -0,0 +1,64 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Determine the Many Type for a property. + *

+ * Scala types require CollectionTypeConverter's to wrap and unwrap from the + * underlying java types used. + *

+ */ +public class DetermineManyType { + + private final boolean withScalaSupport; + private final ManyType scalaBufMany; + private final ManyType scalaSetMany; + private final ManyType scalaMapMany; + + public DetermineManyType(boolean withScalaSupport) { + this.withScalaSupport = withScalaSupport; + if (withScalaSupport){ + + CollectionTypeConverter bufConverter = new ScalaBufferConverter(); + CollectionTypeConverter setConverter = new ScalaSetConverter(); + CollectionTypeConverter mapConverter = new ScalaMapConverter(); + + this.scalaBufMany = new ManyType(ManyType.Underlying.LIST, bufConverter); + this.scalaSetMany = new ManyType(ManyType.Underlying.SET, setConverter); + this.scalaMapMany = new ManyType(ManyType.Underlying.MAP, mapConverter); + + } else { + this.scalaBufMany = null; + this.scalaSetMany = null; + this.scalaMapMany = null; + } + } + + public ManyType getManyType(Class type) { + if (type.equals(List.class)){ + return ManyType.JAVA_LIST; + } + if (type.equals(Set.class)){ + return ManyType.JAVA_SET; + } + if (type.equals(Map.class)){ + return ManyType.JAVA_MAP; + } + if (withScalaSupport){ + // only get in here when scala in classpath + if (type.equals(scala.collection.mutable.Buffer.class)){ + return scalaBufMany; + } + if (type.equals(scala.collection.mutable.Set.class)){ + return scalaSetMany; + } + if (type.equals(scala.collection.mutable.Map.class)){ + return scalaMapMany; + } + } + return null; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ExportedProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ExportedProperty.java new file mode 100644 index 000000000..0a7999944 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ExportedProperty.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.core.InternString; + +/** + * The Exported foreign key and property. + *

+ * Used to for Assoc Manys to create references etc. + *

+ */ +public class ExportedProperty { + + private final String foreignDbColumn; + + private final BeanProperty property; + + private final boolean embedded; + + public ExportedProperty(boolean embedded, String foreignDbColumn, BeanProperty property) { + this.embedded = embedded; + this.foreignDbColumn = InternString.intern(foreignDbColumn); + this.property = property; + } + + /** + * Return true if this is part of an embedded concatinated key. + */ + public boolean isEmbedded() { + return embedded; + } + + /** + * Return the property value from the bean. + */ + public Object getValue(Object bean){ + return property.getValue(bean); + } + + /** + * Return the foreign database column matching this property. + *

+ * We use this foreign database column in the query predicates + * in preference to a parentProperty.idProperty = value. + * Just using the foreign database column avoids triggering + * a join to the 'parent' table. + *

+ */ + public String getForeignDbColumn() { + return foreignDbColumn; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfo.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfo.java new file mode 100644 index 000000000..7b84f8ea7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfo.java @@ -0,0 +1,371 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.deploy.parse.DeployInheritInfo; +import com.avaje.ebeaninternal.server.query.SqlTreeProperties; +import com.avaje.ebeaninternal.server.subclass.SubClassUtil; + +/** + * Represents a node in the Inheritance tree. Holds information regarding Super + * Subclass support. + */ +public class InheritInfo { + + private final String discriminatorStringValue; + private final Object discriminatorValue; + + private final String discriminatorColumn; + + private final int discriminatorType; + + private final int discriminatorLength; + + private final String where; + + private final Class type; + + private final ArrayList children = new ArrayList(); + + /** + * Map of discriminator values to InheritInfo. + */ + private final HashMap discMap; + + /** + * Map of class types to InheritInfo (taking into account subclass proxy classes). + */ + private final HashMap typeMap; + + private final InheritInfo parent; + + private final InheritInfo root; + + private BeanDescriptor descriptor; + + public InheritInfo(InheritInfo r, InheritInfo parent, DeployInheritInfo deploy) { + + this.parent = parent; + this.type = deploy.getType(); + this.discriminatorColumn = InternString.intern(deploy.getDiscriminatorColumn(parent)); + this.discriminatorValue = deploy.getDiscriminatorObjectValue(); + this.discriminatorStringValue = deploy.getDiscriminatorStringValue(); + + this.discriminatorType = deploy.getDiscriminatorType(parent); + this.discriminatorLength = deploy.getDiscriminatorLength(parent); + this.where = InternString.intern(deploy.getWhere()); + + if (r == null) { + // this is a root node + root = this; + discMap = new HashMap(); + typeMap = new HashMap(); + registerWithRoot(this); + + } else { + this.root = r; + // register with the root node... + discMap = null; + typeMap = null; + root.registerWithRoot(this); + } + } + + /** + * Visit all the children in the inheritance tree. + */ + public void visitChildren(InheritInfoVisitor visitor) { + + for (int i = 0; i < children.size(); i++) { + InheritInfo child = children.get(i); + visitor.visit(child); + child.visitChildren(visitor); + } + } + + /** + * return true if anything in the inheritance hierarchy has a relationship + * with a save cascade on it. + */ + public boolean isSaveRecurseSkippable() { + return root.isNodeSaveRecurseSkippable(); + } + + private boolean isNodeSaveRecurseSkippable() { + if (!descriptor.isSaveRecurseSkippable()){ + return false; + } + for (int i = 0; i < children.size(); i++) { + InheritInfo child = children.get(i); + if (!child.isNodeSaveRecurseSkippable()){ + return false; + } + } + return true; + } + + /** + * return true if anything in the inheritance hierarchy has a relationship + * with a delete cascade on it. + */ + public boolean isDeleteRecurseSkippable() { + return root.isNodeDeleteRecurseSkippable(); + } + + private boolean isNodeDeleteRecurseSkippable() { + if (!descriptor.isDeleteRecurseSkippable()) { + return false; + } + for (int i = 0; i < children.size(); i++) { + InheritInfo child = children.get(i); + if (!child.isNodeDeleteRecurseSkippable()) { + return false; + } + } + return true; + } + + /** + * Set the descriptor for this node. + */ + public void setDescriptor(BeanDescriptor descriptor) { + + this.descriptor = descriptor; + } + + /** + * Return the associated BeanDescriptor for this node. + */ + public BeanDescriptor getBeanDescriptor() { + return descriptor; + } + + /** + * Get the bean property additionally looking in the sub types. + */ + public BeanProperty findSubTypeProperty(String propertyName) { + + BeanProperty prop = null; + + for (int i = 0, x=children.size(); i < x; i++) { + InheritInfo childInfo = children.get(i); + + // recursively search this child bean descriptor + prop = childInfo.getBeanDescriptor().findBeanProperty(propertyName); + + if (prop != null){ + return prop; + } + } + + return null; + } + + /** + * Add the local properties for each sub class below this one. + */ + public void addChildrenProperties(SqlTreeProperties selectProps) { + + for (int i = 0, x=children.size(); i < x; i++) { + InheritInfo childInfo = children.get(i); + selectProps.add(childInfo.descriptor.propertiesLocal()); + + childInfo.addChildrenProperties(selectProps); + } + } + + /** + * Return the associated InheritInfo for this DB row read. + */ + public InheritInfo readType(DbReadContext ctx) throws SQLException { + + String discValue = ctx.getDataReader().getString(); + return readType(discValue); + } + + /** + * Return the associated InheritInfo for this discriminator value. + */ + public InheritInfo readType(String discValue) { + + if (discValue == null) { + return null; + } + + InheritInfo typeInfo = root.getType(discValue); + if (typeInfo == null) { + String m = "Inheritance type for discriminator value [" + discValue + "] was not found?"; + throw new PersistenceException(m); + } + + return typeInfo; + } + + /** + * Return the associated InheritInfo for this bean type. + */ + public InheritInfo readType(Class beanType) { + + InheritInfo typeInfo = root.getTypeByClass(beanType); + if (typeInfo == null) { + String m = "Inheritance type for bean type [" + beanType.getName() + "] was not found?"; + throw new PersistenceException(m); + } + + return typeInfo; + } + + /** + * Create an EntityBean for this type. + */ + public Object createBean(boolean vanillaMode) { + return descriptor.createBean(vanillaMode); + } + + /** + * Return the IdBinder for this type. + */ + public IdBinder getIdBinder() { + return descriptor.getIdBinder(); + } + + /** + * return the type. + */ + public Class getType() { + return type; + } + + /** + * Return the root node of the tree. + *

+ * The root has a map of discriminator values to types. + *

+ */ + public InheritInfo getRoot() { + return root; + } + + /** + * Return the parent node. + */ + public InheritInfo getParent() { + return parent; + } + + /** + * Return true if this is abstract node. + */ + public boolean isAbstract() { + return (discriminatorValue == null); + } + + /** + * Return true if this is the root node. + */ + public boolean isRoot() { + return parent == null; + } + + /** + * For a discriminator get the inheritance information for this tree. + */ + public InheritInfo getType(String discValue) { + return discMap.get(discValue); + } + + /** + * Return the InheritInfo for the given bean type. + */ + private InheritInfo getTypeByClass(Class beanType) { + String clsName = SubClassUtil.getSuperClassName(beanType.getName()); + return typeMap.get(clsName); + } + + private void registerWithRoot(InheritInfo info) { + if (info.getDiscriminatorStringValue() != null) { + String stringDiscValue = info.getDiscriminatorStringValue(); + discMap.put(stringDiscValue, info); + } + String clsName = SubClassUtil.getSuperClassName(info.getType().getName()); + typeMap.put(clsName, info); + } + + /** + * Add a child node. + */ + public void addChild(InheritInfo childInfo) { + children.add(childInfo); + } + + /** + * Return the derived where for the discriminator. + */ + public String getWhere() { + + return where; + } + + /** + * Return the column name of the discriminator. + */ + public String getDiscriminatorColumn() { + return discriminatorColumn; + } + + /** + * Return the sql type of the discriminator value. + */ + public int getDiscriminatorType() { + return discriminatorType; + } + + + /** + * Return the length of the discriminator column. + */ + public int getDiscriminatorLength() { + return discriminatorLength; + } + + /** + * Return the discriminator value for this node. + */ + public String getDiscriminatorStringValue() { + return discriminatorStringValue; + } + + public Object getDiscriminatorValue() { + return discriminatorValue; + } + + public String toString() { + return "InheritInfo[" + type.getName() + "] disc[" + discriminatorStringValue + "]"; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfoVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfoVisitor.java new file mode 100644 index 000000000..d244a415f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/InheritInfoVisitor.java @@ -0,0 +1,13 @@ +package com.avaje.ebeaninternal.server.deploy; + +/** + * Used to visit all the InheritInfo in a single inheritance hierarchy. + */ +public interface InheritInfoVisitor { + + /** + * visit the InheritInfo for this node. + */ + public void visit(InheritInfo inheritInfo); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java new file mode 100644 index 000000000..210a72106 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/IntersectionRow.java @@ -0,0 +1,143 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.expression.IdInExpression; +import com.avaje.ebeaninternal.util.DefaultExpressionRequest; + +public class IntersectionRow { + + private final String tableName; + + private final LinkedHashMap values = new LinkedHashMap(); + + private ArrayList excludeIds; + private BeanDescriptor excludeDescriptor; + + public IntersectionRow(String tableName){ + this.tableName = tableName; + } + + /** + * Set Id's to exclude. This is for deleting non-attached detail Id's. + */ + public void setExcludeIds(ArrayList excludeIds, BeanDescriptor excludeDescriptor){ + this.excludeIds = excludeIds; + this.excludeDescriptor = excludeDescriptor; + } + + public void put(String key, Object value){ + values.put(key, value); + } + + + + public SqlUpdate createInsert(EbeanServer server){ + + + BindParams bindParams = new BindParams(); + + StringBuilder sb = new StringBuilder(); + sb.append("insert into ").append(tableName).append(" ("); + + int count = 0; + Iterator> it = values.entrySet().iterator(); + while (it.hasNext()) { + if (count++ > 0){ + sb.append(", "); + } + + Map.Entry entry = it.next(); + sb.append(entry.getKey()); + + bindParams.setParameter(count, entry.getValue()); + } + + sb.append(") values ("); + for (int i = 0; i < count; i++) { + if (i > 0){ + sb.append(", "); + } + sb.append("?"); + } + sb.append(")"); + + return new DefaultSqlUpdate(server, sb.toString(), bindParams); + } + + public SqlUpdate createDelete(EbeanServer server){ + + + BindParams bindParams = new BindParams(); + + + StringBuilder sb = new StringBuilder(); + sb.append("delete from ").append(tableName).append(" where "); + + int count = 0; + Iterator> it = values.entrySet().iterator(); + while (it.hasNext()) { + if (count++ > 0){ + sb.append(" and "); + } + + Map.Entry entry = it.next(); + + sb.append(entry.getKey()); + sb.append(" = ?"); + + bindParams.setParameter(count, entry.getValue()); + } + if (excludeIds != null){ + IdInExpression idIn = new IdInExpression(excludeIds); + + DefaultExpressionRequest er = new DefaultExpressionRequest(excludeDescriptor); + idIn.addSqlNoAlias(er); + idIn.addBindValues(er); + + sb.append(" and not ( "); + sb.append(er.getSql()); + sb.append(" ) "); + + ArrayList bindValues = er.getBindValues(); + for (int i = 0; i < bindValues.size(); i++) { + bindParams.setParameter(++count, bindValues.get(i)); + } + } + + return new DefaultSqlUpdate(server, sb.toString(), bindParams); + } + + public SqlUpdate createDeleteChildren(EbeanServer server) { + + BindParams bindParams = new BindParams(); + + StringBuilder sb = new StringBuilder(); + sb.append("delete from ").append(tableName).append(" where "); + + int count = 0; + Iterator> it = values.entrySet().iterator(); + while (it.hasNext()) { + if (count++ > 0) { + sb.append(" and "); + } + + Map.Entry entry = it.next(); + + sb.append(entry.getKey()); + sb.append(" = ?"); + + bindParams.setParameter(count, entry.getValue()); + } + + return new DefaultSqlUpdate(server, sb.toString(), bindParams); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java new file mode 100644 index 000000000..64dc68c08 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ManyType.java @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Represents the type of a OneToMany or ManyToMany property. + */ +public class ManyType { + + public static final ManyType JAVA_LIST = new ManyType(Underlying.LIST); + public static final ManyType JAVA_SET = new ManyType(Underlying.SET); + public static final ManyType JAVA_MAP = new ManyType(Underlying.MAP); + + public enum Underlying { + LIST, + SET, + MAP + } + + private final SpiQuery.Type queryType; + + private final Underlying underlying; + + private final CollectionTypeConverter typeConverter; + + private ManyType(Underlying underlying) { + this(underlying, null); + } + + public ManyType(Underlying underlying, CollectionTypeConverter typeConverter) { + this.underlying = underlying; + this.typeConverter = typeConverter; + switch (underlying) { + case LIST: + queryType = SpiQuery.Type.LIST; + break; + case SET: + queryType = SpiQuery.Type.SET; + break; + + default: + queryType = SpiQuery.Type.MAP; + break; + } + } + + /** + * Return the matching Query type. + */ + public SpiQuery.Type getQueryType() { + return queryType; + } + + /** + * Return the underlying type. + */ + public Underlying getUnderlying() { + return underlying; + } + + /** + * Return the type converter if there is one. + */ + public CollectionTypeConverter getTypeConverter() { + return typeConverter; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ParamTypeUtil.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ParamTypeUtil.java new file mode 100644 index 000000000..992fb2c6f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ParamTypeUtil.java @@ -0,0 +1,78 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Helper object to find generic parameter types for a given class. + */ +public class ParamTypeUtil { + + /** + * Find and return the parameter type given a generic interface or class. + *

+ * This assumes there is only one generic parameter. + *

+ *

+ * Returns null if no match was found. + *

+ * @param cls the class to search for the parameter type + * @param matchType the type which has the generic parameter + */ + public static Class findParamType(Class cls, Class matchType) { + + // search for: implementing a generic interface + Type paramType = matchByInterfaces(cls, matchType); + if (paramType == null){ + // search for: extending a generic class + Type genericSuperclass = cls.getGenericSuperclass(); + if (genericSuperclass != null){ + paramType = matchParamType(genericSuperclass, matchType); + } + } + + if (paramType instanceof Class){ + // only interested in classes + return (Class)paramType; + } else { + return null; + } + } + + /** + * Check if the type is a generic one with parameters and of the correct type we are + * searching for. Return the parameter type if this matches otherwise return null. + */ + private static Type matchParamType(Type type, Class matchType) { + if (type instanceof ParameterizedType){ + ParameterizedType pt = (ParameterizedType)type; + Type rawType = pt.getRawType(); + boolean isAssignable = matchType.isAssignableFrom((Class) rawType); + if (isAssignable) { + // assume there is only one parameter type + Type[] typeArguments = pt.getActualTypeArguments(); + if (typeArguments.length != 1){ + String m = "Expecting only 1 generic paramater but got "+typeArguments.length+" for "+type; + throw new RuntimeException(m); + } + return typeArguments[0]; + } + } + return null; + } + + /** + * Search the interfaces this class implements. + */ + private static Type matchByInterfaces(Class cls, Class matchType) { + + Type[] gis = cls.getGenericInterfaces(); + for (int i = 0; i < gis.length; i++) { + Type match = matchParamType(gis[i], matchType); + if (match != null){ + return match; + } + } + return null; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/PersistControllerManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/PersistControllerManager.java new file mode 100644 index 000000000..2ac85ed63 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/PersistControllerManager.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.List; +import java.util.logging.Logger; + +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; + +/** + * Default implementation for creating BeanControllers. + */ +public class PersistControllerManager { + + private static final Logger logger = Logger.getLogger(PersistControllerManager.class.getName()); + + private final List list; + + public PersistControllerManager(BootupClasses bootupClasses){ + + list = bootupClasses.getBeanPersistControllers(); + } + + public int getRegisterCount() { + return list.size(); + } + + /** + * Return the BeanPersistController for a given entity type. + */ + public void addPersistControllers(DeployBeanDescriptor deployDesc){ + + for (int i = 0; i < list.size(); i++) { + BeanPersistController c = list.get(i); + if (c.isRegisterFor(deployDesc.getBeanType())){ + logger.fine("BeanPersistController on[" + deployDesc.getFullName() + "] " + c.getClass().getName()); + deployDesc.addPersistController(c); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/PersistListenerManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/PersistListenerManager.java new file mode 100644 index 000000000..ce7a61aaf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/PersistListenerManager.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.List; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; + +/** + * Manages the assignment/registration of BeanPersistListener with their + * respective DeployBeanDescriptor's. + */ +public class PersistListenerManager { + + private static final Logger logger = Logger.getLogger(PersistListenerManager.class.getName()); + + private final List> list; + + public PersistListenerManager(BootupClasses bootupClasses) { + list = bootupClasses.getBeanPersistListeners(); + } + + public int getRegisterCount() { + return list.size(); + } + + /** + * Return the BeanPersistController for a given entity type. + */ + @SuppressWarnings("unchecked") + public void addPersistListeners(DeployBeanDescriptor deployDesc) { + + for (int i = 0; i < list.size(); i++) { + BeanPersistListener c = list.get(i); + if (isRegisterFor(deployDesc.getBeanType(), c)) { + logger.fine("BeanPersistListener on[" + deployDesc.getFullName() + "] " + c.getClass().getName()); + deployDesc.addPersistListener((BeanPersistListener) c); + } + } + } + + public static boolean isRegisterFor(Class beanType, BeanPersistListener c) { + Class listenerEntity = getEntityClass(c.getClass()); + return beanType.equals(listenerEntity); + } + + /** + * Find the entity class given the controller class. + *

+ * This uses reflection to find the generics parameter type. + *

+ */ + private static Class getEntityClass(Class controller) { + + Class cls = ParamTypeUtil.findParamType(controller, BeanPersistListener.class); + if (cls == null) { + String msg = "Could not determine the entity class (generics parameter type) from " + controller + + " using reflection."; + throw new PersistenceException(msg); + } + return cls; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectGetter.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectGetter.java new file mode 100644 index 000000000..3e86c91e5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectGetter.java @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.lang.reflect.Method; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.reflect.BeanReflectGetter; + +/** + * For abstract classes that hold the id property we need to + * use reflection to get the id values some times. + *

+ * This provides the BeanReflectGetter objects to do that. + *

+ * @author rbygrave + */ +public class ReflectGetter { + + /** + * Create a reflection based BeanReflectGetter for getting the + * id from abstract inheritance hierarchy object. + */ + public static BeanReflectGetter create(DeployBeanProperty prop) { + + if (!prop.isId()){ + // not expecting this to ever be used/called + return new NonIdGetter(prop.getFullBeanName()); + + } else { + String property = prop.getFullBeanName(); + Method readMethod = prop.getReadMethod(); + if (readMethod == null){ + String m = "Abstract class with no readMethod for "+property; + throw new RuntimeException(m); + } + return new IdGetter(property, readMethod); + } + } + + public static class IdGetter implements BeanReflectGetter { + + public static final Object[] NO_ARGS = new Object[0]; + + private final Method readMethod; + private final String property; + + public IdGetter(String property, Method readMethod) { + this.property = property; + this.readMethod = readMethod; + } + + public Object get(Object bean) { + try { + return readMethod.invoke(bean, NO_ARGS); + } catch (Exception e) { + String m = "Error on ["+property+"] using readMethod "+readMethod; + throw new RuntimeException(m, e); + } + } + + public Object getIntercept(Object bean) { + return get(bean); + } + } + + public static class NonIdGetter implements BeanReflectGetter { + + private final String property; + + public NonIdGetter(String property) { + this.property = property; + } + + public Object get(Object bean) { + + String m = "Not expecting this method to be called on ["+property + +"] as it is a NON ID property on an abstract class"; + throw new RuntimeException(m); + } + + public Object getIntercept(Object bean) { + return get(bean); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectSetter.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectSetter.java new file mode 100644 index 000000000..47efaa5bc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ReflectSetter.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import java.lang.reflect.Method; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter; + +/** + * A place holder for BeanReflectSetter that should never be called. + *

+ * This is for properties of classes that are abstract and at the root + * of an inheritance hierarchy. + *

+ * @author rbygrave + */ +public class ReflectSetter { + + /** + * Creates place holder objects that should never be called. + */ + public static BeanReflectSetter create(DeployBeanProperty prop) { + + String fullName = prop.getFullBeanName(); + Method writeMethod = prop.getWriteMethod(); + return new RefCalled(fullName, writeMethod); + } + + static class RefCalled implements BeanReflectSetter { + + final String fullName; + final Method writeMethod; + + RefCalled(String fullName, Method writeMethod) { + this.fullName = fullName; + this.writeMethod = writeMethod; + } + public void set(Object bean, Object value) { + Object[] a = new Object[1]; + a[0] = value; + try { + writeMethod.invoke(bean, a); + } catch (Exception e) { + String beanType = bean == null ? "null" : bean.getClass().toString(); + String msg = "Error setting value on "+fullName+" value["+value+"] on type["+beanType+"]"; + throw new RuntimeException(msg, e); + } + } + public void setIntercept(Object bean, Object value) { + String msg = "Not expecting setIntercept to be called. Refer Bug 368"; + throw new RuntimeException(msg); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaBufferConverter.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaBufferConverter.java new file mode 100644 index 000000000..0d95d6ff5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaBufferConverter.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import scala.collection.JavaConversions; + +/** + * Converts between Java List and Scala mutable Buffer. + * + * @author rbygrave + */ +public class ScalaBufferConverter implements CollectionTypeConverter { + +// @SuppressWarnings({ "rawtypes" }) + public Object toUnderlying(Object wrapped) { + throw new IllegalArgumentException("Scala types not supported in this build"); +// if (wrapped instanceof JavaConversions.JListWrapper){ +// return ((JavaConversions.JListWrapper)wrapped).underlying(); +// } +// return null; + } + + public Object toWrapped(Object wrapped) { + throw new IllegalArgumentException("Scala types not supported in this build"); +// if (wrapped instanceof java.util.List){ +// return JavaConversions.asScalaBuffer((java.util.List)wrapped); +// } +// return wrapped; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaMapConverter.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaMapConverter.java new file mode 100644 index 000000000..bcb6e693a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaMapConverter.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import scala.collection.JavaConversions; + +/** + * Converts between Java Map and Scala mutable Map. + * + * @author rbygrave + */ +public class ScalaMapConverter implements CollectionTypeConverter { + +// @SuppressWarnings({ "rawtypes" }) + public Object toUnderlying(Object wrapped) { + throw new IllegalArgumentException("Scala types not supported in this build"); +// if (wrapped instanceof JavaConversions.JMapWrapper){ +// return ((JavaConversions.JMapWrapper)wrapped).underlying(); +// } +// return null; + } + + public Object toWrapped(Object wrapped) { + throw new IllegalArgumentException("Scala types not supported in this build"); +// if (wrapped instanceof java.util.Map){ +// return JavaConversions.mapAsScalaMap((java.util.Map)wrapped); +// } +// return wrapped; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaSetConverter.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaSetConverter.java new file mode 100644 index 000000000..19b029866 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ScalaSetConverter.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import scala.collection.JavaConversions; +import scala.collection.convert.DecorateAsScala; + +/** + * Converts between Java Set and Scala mutable Set. + * + * @author rbygrave + */ +public class ScalaSetConverter implements CollectionTypeConverter { + +// @SuppressWarnings({ "rawtypes" }) + public Object toUnderlying(Object wrapped) { + throw new IllegalArgumentException("Scala types not supported in this build"); +// if (wrapped instanceof JavaConversions.JSetWrapper){ +// return ((JavaConversions.JSetWrapper)wrapped).underlying(); +// } +// return null; + } + + public Object toWrapped(Object wrapped) { + throw new IllegalArgumentException("Scala types not supported in this build"); +// if (wrapped instanceof java.util.Set){ +// return JavaConversions.asScalaSet((java.util.Set)wrapped); +// } +// return wrapped; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java new file mode 100644 index 000000000..e43f1aeb5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoin.java @@ -0,0 +1,227 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.avaje.ebeaninternal.server.query.SqlBeanLoad; + +import java.sql.SQLException; +import java.util.LinkedHashMap; + +/** + * Represents a join to another table. + */ +public final class TableJoin { + + + public static final String NEW_LINE = "\n"; + + public static final String LEFT_OUTER = "left outer join"; + + public static final String JOIN = "join"; + + /** + * Flag set when the imported key maps to the primary key. + * This occurs for intersection tables (ManyToMany). + */ + private final boolean importedPrimaryKey; + + /** + * The joined table. + */ + private final String table; + + /** + * The type of join. LEFT OUTER etc. + */ + private final String type; + + /** + * The persist cascade info. + */ + private final BeanCascadeInfo cascadeInfo; + + /** + * Properties as an array. + */ + private final BeanProperty[] properties; + + /** + * Columns as an array. + */ + private final TableJoinColumn[] columns; + + /** + * Create a TableJoin. + */ + public TableJoin(DeployTableJoin deploy, LinkedHashMap propMap) { + + this.importedPrimaryKey = deploy.isImportedPrimaryKey(); + this.table = InternString.intern(deploy.getTable()); + this.type = InternString.intern(deploy.getType()); + this.cascadeInfo = deploy.getCascadeInfo(); + + DeployTableJoinColumn[] deployCols = deploy.columns(); + this.columns = new TableJoinColumn[deployCols.length]; + for (int i = 0; i < deployCols.length; i++) { + this.columns[i] = new TableJoinColumn(deployCols[i]); + } + + DeployBeanProperty[] deployProps = deploy.properties(); + if (deployProps.length > 0 && propMap == null){ + throw new NullPointerException("propMap is null?"); + } + + this.properties = new BeanProperty[deployProps.length]; + for (int i = 0; i < deployProps.length; i++) { + BeanProperty prop = propMap.get(deployProps[i].getName()); + this.properties[i] = prop; + } + + } + + /** + * Create a tableJoin based on this object but with different alias. + */ + public TableJoin createWithAlias(String localAlias, String foreignAlias) { + + return new TableJoin(this, localAlias, foreignAlias); + } + + /** + * Construct a copy but with different table alias'. + */ + private TableJoin(TableJoin join, String localAlias, String foreignAlias){ + + // copy the immutable fields + this.importedPrimaryKey = join.importedPrimaryKey; + this.table = join.table; + this.type = join.type; + this.cascadeInfo = join.cascadeInfo; + this.properties = join.properties; + this.columns = join.columns; + } + + + public String toString() { + StringBuilder sb = new StringBuilder(30); + sb.append(type).append(" ").append(table).append(" "); + for (int i = 0; i < columns.length; i++) { + sb.append(columns[i]).append(" "); + } + return sb.toString(); + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + for (int i = 0, x = properties.length; i < x; i++) { + properties[i].appendSelect(ctx, subQuery); + } + } + + public void load(SqlBeanLoad sqlBeanLoad) throws SQLException { + for (int i = 0, x = properties.length; i < x; i++) { + properties[i].load(sqlBeanLoad); + } + } + + public Object readSet(DbReadContext ctx, Object bean, Class type) throws SQLException { + for (int i = 0, x = properties.length; i < x; i++) { + properties[i].readSet(ctx, bean, type); + } + return null; + } + + /** + * Return true if the imported foreign key maps to the primary key. + */ + public boolean isImportedPrimaryKey() { + return importedPrimaryKey; + } + + /** + * Return the persist info. + */ + public BeanCascadeInfo getCascadeInfo() { + return cascadeInfo; + } + + /** + * Return the join columns. + */ + public TableJoinColumn[] columns() { + return columns; + } + + + /** + * For secondary table joins returns the properties mapped to that table. + */ + public BeanProperty[] properties() { + return properties; + } + + /** + * Return the joined table name. + */ + public String getTable() { + return table; + } + + /** + * Return the type of join. LEFT OUTER JOIN etc. + */ + public String getType() { + return type; + } + + /** + * Return true if this join is a left outer join. + */ + public boolean isOuterJoin() { + return type.equals(LEFT_OUTER); + } + + public boolean addJoin(boolean forceOuterJoin, String prefix, DbSqlContext ctx) { + + String[] names = SplitName.split(prefix); + String a1 = ctx.getTableAlias(names[0]); + String a2 = ctx.getTableAlias(prefix); + + return addJoin(forceOuterJoin, a1, a2, ctx); + } + + public boolean addJoin(boolean forceOuterJoin, String a1, String a2, DbSqlContext ctx) { + + ctx.addJoin(forceOuterJoin?LEFT_OUTER:type, table, columns(), a1, a2); + + return forceOuterJoin || LEFT_OUTER.equals(type); + } + + /** + * Explicitly add a (non-outer) join. + */ + public void addInnerJoin(String a1, String a2, DbSqlContext ctx) { + ctx.addJoin(JOIN, table, columns(), a1, a2); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java new file mode 100644 index 000000000..eba5ba090 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/TableJoinColumn.java @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; + +/** + * A join pair of local and foreign properties. + */ +public class TableJoinColumn { + + /** + * The local database column name. + */ + private final String localDbColumn; + + /** + * The foreign database column name. + */ + private final String foreignDbColumn; + + private final boolean insertable; + + private final boolean updateable; + + /** + * Create the pair. + */ + public TableJoinColumn(DeployTableJoinColumn deploy) { + this.localDbColumn = InternString.intern(deploy.getLocalDbColumn()); + this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn()); + this.insertable = deploy.isInsertable(); + this.updateable = deploy.isUpdateable(); + } + + public String toString() { + return localDbColumn+" = "+foreignDbColumn; + } + + + /** + * Return the foreign database column name. + */ + public String getForeignDbColumn() { + return foreignDbColumn; + } + + /** + * Return the local database column name. + */ + public String getLocalDbColumn() { + return localDbColumn; + } + + /** + * Return true if this column should be insertable. + */ + public boolean isInsertable() { + return insertable; + } + + /** + * Return true if this column should be updateable. + */ + public boolean isUpdateable() { + return updateable; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ebeandeploy.dtd b/src/main/java/com/avaje/ebeaninternal/server/deploy/ebeandeploy.dtd new file mode 100644 index 000000000..37a869e4e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ebeandeploy.dtd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/CounterFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/CounterFactory.java new file mode 100644 index 000000000..717ed5a47 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/CounterFactory.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Types; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +/** + * Creates "Counter" GeneratedProperty for various types of number. + *

+ * Aka, Integer, Long, Short etc. + *

+ */ +public class CounterFactory { + + final GeneratedCounterInteger integerCounter = new GeneratedCounterInteger(); + + final GeneratedCounterLong longCounter = new GeneratedCounterLong(); + + public void setCounter(DeployBeanProperty property) { + + property.setGeneratedProperty(createCounter(property)); + } + + /** + * Create the GeneratedProperty based on the property type. + */ + private GeneratedProperty createCounter(DeployBeanProperty property) { + + Class propType = property.getPropertyType(); + if (propType.equals(Integer.class) || propType.equals(int.class)) { + return integerCounter; + } + if (propType.equals(Long.class) || propType.equals(long.class)) { + return longCounter; + } + + int type = getType(propType); + return new GeneratedCounter(type); + } + + private int getType(Class propType){ + if (propType.equals(Short.class) || propType.equals(short.class)){ + return Types.TINYINT; + } + if (propType.equals(BigDecimal.class)){ + return Types.DECIMAL; + } + if (propType.equals(Double.class) || propType.equals(double.class)){ + return Types.DOUBLE; + } + if (propType.equals(Float.class) || propType.equals(float.class)){ + return Types.REAL; + } + if (propType.equals(BigInteger.class)){ + return Types.BIGINT; + } + String msg = "Can not support Counter for type "+propType.getName(); + throw new PersistenceException(msg); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounter.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounter.java new file mode 100644 index 000000000..8a39d5174 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounter.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * A general number counter for various number types. + */ +public class GeneratedCounter implements GeneratedProperty { + + final int numberType; + + public GeneratedCounter(int numberType) { + this.numberType = numberType; + } + + /** + * Always returns a 1. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + Integer i = Integer.valueOf(1); + return BasicTypeConverter.convert(i, numberType); + } + + /** + * Increments the current value by one. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + Number currVal = (Number) prop.getValue(bean); + Integer nextVal = Integer.valueOf(currVal.intValue() + 1); + return BasicTypeConverter.convert(nextVal, numberType); + } + + /** + * Include this in every update. + */ + public boolean includeInUpdate() { + return true; + } + + /** + * Include this in every insert setting initial counter value to 1. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterInteger.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterInteger.java new file mode 100644 index 000000000..a1e6562da --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterInteger.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to create a counter version column for Integer. + */ +public class GeneratedCounterInteger implements GeneratedProperty { + + public GeneratedCounterInteger() { + + } + + /** + * Always returns a 1. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return Integer.valueOf(1); + } + + /** + * Increments the current value by one. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + Integer i = (Integer) prop.getValue(bean); + return Integer.valueOf(i.intValue() + 1); + } + + /** + * Include this in every update. + */ + public boolean includeInUpdate() { + return true; + } + + /** + * Include this in every insert setting initial counter value to 1. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterLong.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterLong.java new file mode 100644 index 000000000..844bb4d4b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedCounterLong.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to create a counter version column for Long. + */ +public class GeneratedCounterLong implements GeneratedProperty { + + public GeneratedCounterLong() { + + } + + /** + * Always returns a 1. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return Long.valueOf(1); + } + + /** + * Increments the current value by one. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + Long i = (Long) prop.getValue(bean); + return Long.valueOf(i.longValue() + 1); + } + + /** + * Include this in every update. + */ + public boolean includeInUpdate() { + return true; + } + + /** + * Include this in every insert setting initial counter value to 1. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertDate.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertDate.java new file mode 100644 index 000000000..fcdd8a970 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertDate.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.util.Date; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to generate a (java.util.Date) timestamp when a bean is inserted. + */ +public class GeneratedInsertDate implements GeneratedProperty { + + /** + * Return the current time as a Timestamp. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return new Date(System.currentTimeMillis()); + } + + /** + * Just returns the beans original insert timestamp value. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + return prop.getValue(bean); + } + + /** + * Return false. + */ + public boolean includeInUpdate() { + return false; + } + + /** + * Return true. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertLong.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertLong.java new file mode 100644 index 000000000..081219278 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertLong.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to generate a (Long) timestamp when a bean is inserted. + */ +public class GeneratedInsertLong implements GeneratedProperty { + + /** + * Return the current time as a Timestamp. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return Long.valueOf(System.currentTimeMillis()); + } + + /** + * Just returns the beans original insert timestamp value. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + return prop.getValue(bean); + } + + /** + * Return false. + */ + public boolean includeInUpdate() { + return false; + } + + /** + * Return true. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertTimestamp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertTimestamp.java new file mode 100644 index 000000000..53f9e7d68 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedInsertTimestamp.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.sql.Timestamp; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to generate a timestamp when a bean is inserted. + */ +public class GeneratedInsertTimestamp implements GeneratedProperty { + + /** + * Return the current time as a Timestamp. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return new Timestamp(System.currentTimeMillis()); + } + + /** + * Just returns the beans original insert timestamp value. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + return prop.getValue(bean); + } + + /** + * Return false. + */ + public boolean includeInUpdate() { + return false; + } + + /** + * Return true. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedProperty.java new file mode 100644 index 000000000..5a3e754c5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedProperty.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to generate values for a property rather than have then set by the user. + * For example generate the update timestamp when a bean is updated. + */ +public interface GeneratedProperty { + + /** + * Get the generated insert value for a specific property of a bean. + */ + public Object getInsertValue(BeanProperty prop, Object bean); + + /** + * Get the generated update value for a specific property of a bean. + */ + public Object getUpdateValue(BeanProperty prop, Object bean); + + /** + * Return true if this should always be includes in an update statement. + *

+ * Used to include GeneratedUpdateTimestamp in dynamic table updates. + *

+ */ + public boolean includeInUpdate(); + + /** + * Return true if this should be included in insert statements. + */ + public boolean includeInInsert(); + + /** + * Return true if the GeneratedProperty implies the DDL to create the DB + * column should have a not null constraint. + */ + public boolean isDDLNotNullable(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java new file mode 100644 index 000000000..ef81bb272 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.math.BigDecimal; +import java.util.HashSet; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +/** + * Default implementation of GeneratedPropertyFactory. + */ +public class GeneratedPropertyFactory { + + CounterFactory counterFactory; + + InsertTimestampFactory insertFactory; + + UpdateTimestampFactory updateFactory; + + HashSet numberTypes = new HashSet(); + + public GeneratedPropertyFactory() { + counterFactory = new CounterFactory(); + insertFactory = new InsertTimestampFactory(); + updateFactory = new UpdateTimestampFactory(); + + + numberTypes.add(Integer.class.getName()); + numberTypes.add(int.class.getName()); + numberTypes.add(Long.class.getName()); + numberTypes.add(long.class.getName()); + numberTypes.add(Short.class.getName()); + numberTypes.add(short.class.getName()); + numberTypes.add(Double.class.getName()); + numberTypes.add(double.class.getName()); + numberTypes.add(BigDecimal.class.getName()); + } + + private boolean isNumberType(String typeClassName) { + return numberTypes.contains(typeClassName); + } + + public void setVersion(DeployBeanProperty property) { + if (isNumberType(property.getPropertyType().getName())) { + setCounter(property); + } else { + setUpdateTimestamp(property); + } + } + + public void setCounter(DeployBeanProperty property) { + + counterFactory.setCounter(property); + } + + public void setInsertTimestamp(DeployBeanProperty property) { + + insertFactory.setInsertTimestamp(property); + } + + public void setUpdateTimestamp(DeployBeanProperty property) { + + updateFactory.setUpdateTimestamp(property); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateDate.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateDate.java new file mode 100644 index 000000000..e89a304f5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateDate.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.util.Date; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Generate a (java.util.Date) Timestamp whenever the bean is inserted or + * updated. + */ +public class GeneratedUpdateDate implements GeneratedProperty { + + /** + * Return now as a Timestamp. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return new Date(System.currentTimeMillis()); + } + + /** + * Return now as a Timestamp. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + return new Date(System.currentTimeMillis()); + } + + /** + * For dynamic table updates make sure this is included. + */ + public boolean includeInUpdate() { + return true; + } + + /** + * Include this in every insert. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateLong.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateLong.java new file mode 100644 index 000000000..11917df53 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateLong.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Generate a (Long) Timestamp whenever the bean is inserted or updated. + */ +public class GeneratedUpdateLong implements GeneratedProperty { + + /** + * Return now as a Timestamp. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return Long.valueOf(System.currentTimeMillis()); + } + + /** + * Return now as a Timestamp. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + return Long.valueOf(System.currentTimeMillis()); + } + + /** + * For dynamic table updates make sure this is included. + */ + public boolean includeInUpdate() { + return true; + } + + /** + * Include this in every insert. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateTimestamp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateTimestamp.java new file mode 100644 index 000000000..ee627f19f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedUpdateTimestamp.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.sql.Timestamp; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Generate a Timestamp whenever the bean is inserted or updated. + */ +public class GeneratedUpdateTimestamp implements GeneratedProperty { + + /** + * Return now as a Timestamp. + */ + public Object getInsertValue(BeanProperty prop, Object bean) { + return new Timestamp(System.currentTimeMillis()); + } + + /** + * Return now as a Timestamp. + */ + public Object getUpdateValue(BeanProperty prop, Object bean) { + return new Timestamp(System.currentTimeMillis()); + } + + /** + * For dynamic table updates make sure this is included. + */ + public boolean includeInUpdate() { + return true; + } + + /** + * Include this in every insert. + */ + public boolean includeInInsert() { + return true; + } + + public boolean isDDLNotNullable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/InsertTimestampFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/InsertTimestampFactory.java new file mode 100644 index 000000000..5b048449e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/InsertTimestampFactory.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.sql.Timestamp; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +/** + * Helper for creating Insert timestamp GeneratedProperty objects. + */ +public class InsertTimestampFactory { + + final GeneratedInsertTimestamp timestamp = new GeneratedInsertTimestamp(); + + final GeneratedInsertDate utilDate = new GeneratedInsertDate(); + + final GeneratedInsertLong longTime = new GeneratedInsertLong(); + + public void setInsertTimestamp(DeployBeanProperty property) { + + property.setGeneratedProperty(createInsertTimestamp(property)); + } + + /** + * Create the insert GeneratedProperty depending on the property type. + */ + public GeneratedProperty createInsertTimestamp(DeployBeanProperty property) { + + Class propType = property.getPropertyType(); + if (propType.equals(Timestamp.class)) { + return timestamp; + } + if (propType.equals(java.util.Date.class)) { + return utilDate; + } + if (propType.equals(Long.class) || propType.equals(long.class)) { + return longTime; + } + + //TODO: Support JODA Time objects ... perhaps others? + + String msg = "Generated Insert Timestamp not supported on "+propType.getName(); + throw new PersistenceException(msg); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/UpdateTimestampFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/UpdateTimestampFactory.java new file mode 100644 index 000000000..372328f7b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/UpdateTimestampFactory.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.generatedproperty; + +import java.sql.Timestamp; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +/** + * Helper for creating Update timestamp GeneratedProperty objects. + */ +public class UpdateTimestampFactory { + + final GeneratedUpdateTimestamp timestamp = new GeneratedUpdateTimestamp(); + + final GeneratedUpdateDate utilDate = new GeneratedUpdateDate(); + + final GeneratedUpdateLong longTime = new GeneratedUpdateLong(); + + public void setUpdateTimestamp(DeployBeanProperty property) { + + property.setGeneratedProperty(createUpdateTimestamp(property)); + } + + /** + * Create the update GeneratedProperty depending on the property type. + */ + private GeneratedProperty createUpdateTimestamp(DeployBeanProperty property) { + + Class propType = property.getPropertyType(); + if (propType.equals(Timestamp.class)) { + return timestamp; + } + if (propType.equals(java.util.Date.class)) { + return utilDate; + } + if (propType.equals(Long.class) || propType.equals(long.class)) { + return longTime; + } + + //TODO: Support JODA Time objects ... perhaps others? + + String msg = "Generated update Timestamp not supported on "+propType.getName(); + throw new PersistenceException(msg); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/package.html b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/package.html new file mode 100644 index 000000000..5aadfd6dd --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/package.html @@ -0,0 +1,9 @@ + + + + Counter, Insert Timestamp, Update Timestamp support + + +Counter, Insert Timestamp, Update Timestamp support + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinder.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinder.java new file mode 100644 index 000000000..f2416b483 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinder.java @@ -0,0 +1,192 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Binds id values to prepared statements. + */ +public interface IdBinder { + + /** + * Initialise the binder. + */ + public void initialise(); + + /** + * Convert the Id value as a Lucene string term. + */ + public String writeTerm(Object idValue); + + /** + * Convert the Lucene string term into an Id value. + */ + public Object readTerm(String idTermValue); + + /** + * Write the Id value to binary DataOuput. + */ + public void writeData(DataOutput dataOutput, Object idValue) throws IOException; + + /** + * Read the Id value from the binary DataInput. + */ + public Object readData(DataInput dataInput) throws IOException; + + /** + * Adds RDN's to the LdapName using the id value. + */ + public void createLdapNameById(LdapName name, Object id) throws InvalidNameException; + + /** + * Adds RDN's to the LdapName using the id value from the bean. + */ + public void createLdapNameByBean(LdapName name, Object bean) throws InvalidNameException; + + /** + * Return the name(s) of the Id property(s). + * Comma delimited if there is more than one. + *

+ * This can be used to include in a query. + *

+ */ + public String getIdProperty(); + + /** + * Find a BeanProperty that is mapped to the database column. + */ + public BeanProperty findBeanProperty(String dbColumnName); + + /** + * Return the number of scalar properties for this id. + */ + public int getPropertyCount(); + + /** + * Return false if the id is a simple scalar and false if it is embedded or concatenated. + */ + public boolean isComplexId(); + + /** + * Return the default order by that may need to be used if the query includes a many property. + */ + public String getDefaultOrderBy(); + + public String getOrderBy(String pathPrefix, boolean ascending); + + /** + * Return the values as an array of scalar bindable values. + *

+ * For concatenated keys that use an Embedded bean or multiple id properties + * this determines the field values are returns them as an Object array. + *

+ *

+ * Added primarily for Query.addWhere().add(Expr.idEq()) support. + *

+ */ + public Object[] getBindValues(Object idValue); + + /** + * Return the id values for a given bean. + */ + public Object[] getIdValues(Object bean); + + /** + * Build a string of the logical expressions. + *

+ * Typically used to build a id = ? string. + *

+ */ + public String getAssocOneIdExpr(String prefix, String operator); + + /** + * Return the logical id in expression taking into account embedded id's. + */ + public String getAssocIdInExpr(String prefix); + + /** + * Binds an id value to a prepared statement. + */ + public void bindId(DataBind dataBind, Object value) throws SQLException; + + /** + * Bind the id value to a SqlUpdate statement. + */ + public void bindId(DefaultSqlUpdate sqlUpdate, Object value); + + public void addIdInBindValue(SpiExpressionRequest request, Object value); + + /** + * Return the sql for binding the id using an IN clause. + */ + public String getBindIdInSql(String baseTableAlias); + + /** + * Return the binding expression (like "?" or "(?,?)")for the Id. + */ + public String getIdInValueExpr(int size); + + /** + * Same as getIdInValueExpr but for delete by id. + */ + public String getIdInValueExprDelete(int size); + + public void buildSelectExpressionChain(String prefix, List selectChain); + + /** + * Read the id value from the result set and set it to the bean also returning it. + */ + public Object readSet(DbReadContext ctx, Object bean) throws SQLException; + + /** + * Ignore the appropriate number of scalar properties for this id. + */ + public void loadIgnore(DbReadContext ctx); + + /** + * Read the id value from the result set and return it. + */ + public Object read(DbReadContext ctx) throws SQLException; + + /** + * Append to the select clause. + */ + public void appendSelect(DbSqlContext ctx, boolean subQuery); + + /** + * Return the sql for binding the id to. This includes table alias and columns that make up the id. + */ + public String getBindIdSql(String baseTableAlias); + + /** + * Return the id properties in flat form. + */ + public BeanProperty[] getProperties(); + + /** + * Cast or convert the Id value if necessary and optionally set it. + *

+ * The Id value is not assumed to be the correct type so it is converted to + * the correct type. Typically this is because we could get a Integer, Long + * or BigDecimal depending on the JDBC driver and situation. + *

+ *

+ * If the bean is not null, then the value is set to the bean. + *

+ */ + public Object convertSetId(Object idValue, Object bean); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmbedded.java new file mode 100644 index 000000000..498ddbe24 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmbedded.java @@ -0,0 +1,455 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.avaje.ebeaninternal.server.type.DataBind; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.persistence.PersistenceException; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +/** + * Bind an Id that is an Embedded bean. + */ +public final class IdBinderEmbedded implements IdBinder { + + private final BeanPropertyAssocOne embIdProperty; + + private final boolean idInExpandedForm; + + private BeanProperty[] props; + + private BeanDescriptor idDesc; + + private String idInValueSql; + + + public IdBinderEmbedded(boolean idInExpandedForm, BeanPropertyAssocOne embIdProperty) { + this.idInExpandedForm = idInExpandedForm; + this.embIdProperty = embIdProperty; + } + + public void initialise() { + this.idDesc = embIdProperty.getTargetDescriptor(); + this.props = embIdProperty.getProperties(); + this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed(); + } + + private String idInExpanded() { + + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(" and "); + } + sb.append(embIdProperty.getName()); + sb.append("."); + sb.append(props[i].getName()); + sb.append("=?"); + } + sb.append(")"); + + return sb.toString(); + } + + private String idInCompressed() { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("?"); + } + sb.append(")"); + + return sb.toString(); + } + + public String getOrderBy(String pathPrefix, boolean ascending){ + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(", "); + } + if (pathPrefix != null){ + sb.append(pathPrefix).append("."); + } + + sb.append(embIdProperty.getName()).append("."); + sb.append(props[i].getName()); + if (!ascending){ + sb.append(" desc"); + } + } + return sb.toString(); + } + + + public void createLdapNameById(LdapName name, Object id) throws InvalidNameException { + + for (int i = 0; i < props.length; i++) { + Object v = props[i].getValue(id); + Rdn rdn = new Rdn(props[i].getDbColumn(), v); + name.add(rdn); + } + } + + + + public void createLdapNameByBean(LdapName name, Object bean) throws InvalidNameException { + Object id = embIdProperty.getValue(bean); + createLdapNameById(name, id); + } + + public BeanDescriptor getIdBeanDescriptor() { + return idDesc; + } + + public int getPropertyCount() { + return props.length; + } + + public String getIdProperty() { + return embIdProperty.getName(); + } + + public void buildSelectExpressionChain(String prefix, List selectChain) { + + prefix = SplitName.add(prefix, embIdProperty.getName()); + + for (int i = 0; i < props.length; i++) { + props[i].buildSelectExpressionChain(prefix, selectChain); + } + } + + public BeanProperty findBeanProperty(String dbColumnName) { + for (int i = 0; i < props.length; i++) { + if (dbColumnName.equalsIgnoreCase(props[i].getDbColumn())) { + return props[i]; + } + } + return null; + } + + public boolean isComplexId() { + return true; + } + + public String getDefaultOrderBy() { + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(","); + } + + sb.append(embIdProperty.getName()); + sb.append("."); + sb.append(props[i].getName()); + } + + return sb.toString(); + } + + public BeanProperty[] getProperties() { + return props; + } + + public void addIdInBindValue(SpiExpressionRequest request, Object value) { + for (int i = 0; i < props.length; i++) { + request.addBindValue(props[i].getValue(value)); + } + } + + public String getIdInValueExprDelete(int size) { + if (!idInExpandedForm){ + return getIdInValueExpr(size); + } + + StringBuilder sb = new StringBuilder(); + sb.append("("); + + for (int j = 0; j < size; j++) { + if (j > 0){ + sb.append(" or "); + } + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(" and "); + } + sb.append(props[i].getDbColumn()); + sb.append("=?"); + } + sb.append(")"); + } + sb.append(") "); + return sb.toString(); + } + + public String getIdInValueExpr(int size) { + + StringBuilder sb = new StringBuilder(); + + if (!idInExpandedForm){ + sb.append(" in"); + } + sb.append(" ("); + for (int i = 0; i < size; i++) { + if (i > 0){ + if (idInExpandedForm) { + sb.append(" or "); + } else { + sb.append(","); + } + } + sb.append(idInValueSql); + } + sb.append(") "); + return sb.toString(); + } + + public String getIdInValueExpr() { + return idInValueSql; + } + + /** + * Convert the lucene string term value into embedded id. + */ + public Object readTerm(String idTermValue) { + + String[] split = idTermValue.split("|"); + if (split.length != props.length){ + String msg = "Failed to split ["+idTermValue+"] using | for id."; + throw new PersistenceException(msg); + } + Object embId = idDesc.createVanillaBean(); + for (int i = 0; i < props.length; i++) { + Object v = props[i].getScalarType().parse(split[i]); + props[i].setValue(embId, v); + } + return embId; + } + + /** + * Write the embedded id as a Lucene string term value. + */ + public String writeTerm(Object idValue) { + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < props.length; i++) { + Object v = props[i].getValue(idValue); + String formatValue = props[i].getScalarType().format(v); + if (i > 0){ + sb.append("|"); + } + sb.append(formatValue); + } + return sb.toString(); + } + + public Object[] getIdValues(Object bean) { + bean = embIdProperty.getValue(bean); + Object[] bindvalues = new Object[props.length]; + for (int i = 0; i < props.length; i++) { + bindvalues[i] = props[i].getValue(bean); + } + return bindvalues; + } + + public Object[] getBindValues(Object value) { + + Object[] bindvalues = new Object[props.length]; + for (int i = 0; i < props.length; i++) { + bindvalues[i] = props[i].getValue(value); + } + return bindvalues; + } + + public void bindId(DefaultSqlUpdate sqlUpdate, Object value) { + for (int i = 0; i < props.length; i++) { + Object embFieldValue = props[i].getValue(value); + sqlUpdate.addParameter(embFieldValue); + } + } + + public void bindId(DataBind dataBind, Object value) throws SQLException { + + for (int i = 0; i < props.length; i++) { + Object embFieldValue = props[i].getValue(value); + props[i].bind(dataBind, embFieldValue); + } + } + + public Object readData(DataInput dataInput) throws IOException { + + Object embId = idDesc.createVanillaBean(); + boolean notNull = true; + + for (int i = 0; i < props.length; i++) { + Object value = props[i].readData(dataInput); + props[i].setValue(embId, value); + if (value == null) { + notNull = false; + } + } + + if (notNull) { + return embId; + } else { + return null; + } + } + + public void writeData(DataOutput dataOutput, Object idValue) throws IOException { + for (int i = 0; i < props.length; i++) { + Object embFieldValue = props[i].getValue(idValue); + props[i].writeData(dataOutput, embFieldValue); + } + } + + public void loadIgnore(DbReadContext ctx) { + for (int i = 0; i < props.length; i++) { + props[i].loadIgnore(ctx); + } + } + + public Object read(DbReadContext ctx) throws SQLException { + + Object embId = idDesc.createVanillaBean(); + boolean notNull = true; + + for (int i = 0; i < props.length; i++) { + Object value = props[i].readSet(ctx, embId, null); + if (value == null) { + notNull = false; + } + } + + if (notNull) { + return embId; + } else { + return null; + } + } + + public Object readSet(DbReadContext ctx, Object bean) throws SQLException { + + Object embId = read(ctx); + if (embId != null) { + embIdProperty.setValue(bean, embId); + return embId; + } else { + return null; + } + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + for (int i = 0; i < props.length; i++) { + props[i].appendSelect(ctx, subQuery); + } + } + + public String getAssocIdInExpr(String prefix) { + + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(","); + } + if (prefix != null) { + sb.append(prefix); + sb.append("."); + } + sb.append(props[i].getName()); + } + sb.append(")"); + return sb.toString(); + } + + public String getAssocOneIdExpr(String prefix, String operator) { + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(" and "); + } + if (prefix != null) { + sb.append(prefix); + sb.append("."); + } + + sb.append(embIdProperty.getName()); + sb.append("."); + sb.append(props[i].getName()); + sb.append(operator); + } + return sb.toString(); + } + + public String getBindIdSql(String baseTableAlias) { + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(" and "); + } + if (baseTableAlias != null) { + sb.append(baseTableAlias); + sb.append("."); + } + sb.append(props[i].getDbColumn()); + sb.append(" = ? "); + } + return sb.toString(); + } + + public String getBindIdInSql(String baseTableAlias) { + + if (idInExpandedForm){ + return ""; + } + + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(","); + } + if (baseTableAlias != null){ + sb.append(baseTableAlias); + sb.append("."); + } + sb.append(props[i].getDbColumn()); + } + sb.append(")"); + return sb.toString(); + } + + public Object convertSetId(Object idValue, Object bean) { + + // can not cast/convert if it is embedded + if (bean != null) { + // support PropertyChangeSupport + embIdProperty.setValueIntercept(bean, idValue); + } + + return idValue; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmpty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmpty.java new file mode 100644 index 000000000..66037273e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderEmpty.java @@ -0,0 +1,152 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * For beans with no id properties AKA report type beans. + */ +public final class IdBinderEmpty implements IdBinder { + + private static final String bindIdSql = ""; + + private static final BeanProperty[] properties = new BeanProperty[0]; + + public IdBinderEmpty() { + + } + public void initialise(){ + + } + + public Object readTerm(String idTermValue) { + return null; + } + + public String writeTerm(Object idValue) { + return null; + } + + public String getOrderBy(String pathPrefix, boolean ascending){ + return pathPrefix; + } + + public void buildSelectExpressionChain(String prefix, List selectChain) { + } + + public void createLdapNameById(LdapName name, Object id) throws InvalidNameException { + } + + public void createLdapNameByBean(LdapName name, Object bean) throws InvalidNameException { + } + + public int getPropertyCount() { + return 0; + } + + public String getIdProperty() { + return null; + } + + public BeanProperty findBeanProperty(String dbColumnName) { + return null; + } + public boolean isComplexId(){ + return true; + } + + + public String getDefaultOrderBy() { + // this should never happen? + return ""; + } + + public BeanProperty[] getProperties() { + return properties; + } + + public String getBindIdSql(String baseTableAlias) { + return bindIdSql; + } + + public String getAssocOneIdExpr(String prefix, String operator){ + return null; + } + + public String getAssocIdInExpr(String prefix) { + return null; + } + + public void addIdInBindValue(SpiExpressionRequest request, Object value) { + + } + + public String getIdInValueExprDelete(int size) { + return getIdInValueExpr(size); + } + + public String getIdInValueExpr(int size) { + return ""; + } + + + public String getBindIdInSql(String baseTableAlias) { + return null; + } + + public Object[] getIdValues(Object bean){ + return null; + } + + public Object[] getBindValues(Object idValue){ + return new Object[]{idValue}; + } + + public void bindId(DefaultSqlUpdate sqlUpdate, Object value) { + + } + + public void bindId(DataBind dataBind, Object value) throws SQLException { + + } + + public void loadIgnore(DbReadContext ctx) { + } + + public Object readSet(DbReadContext ctx, Object bean) throws SQLException { + return null; + } + + public Object read(DbReadContext ctx) throws SQLException { + return null; + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + } + + public Object convertSetId(Object idValue, Object bean){ + return idValue; + } + + public Object readData(DataInput dataOutput) throws IOException { + return null; + } + + public void writeData(DataOutput dataOutput, Object idValue) throws IOException { + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderFactory.java new file mode 100644 index 000000000..55d946abb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderFactory.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.id; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Creates the appropriate IdConvertSet depending on the type of Id property(s). + */ +public class IdBinderFactory { + + private static final IdBinderEmpty EMPTY = new IdBinderEmpty(); + + private final boolean idInExpandedForm; + + public IdBinderFactory(boolean idInExpandedForm) { + this.idInExpandedForm = idInExpandedForm; + } + + /** + * Create the IdConvertSet for the given type of Id properties. + */ + public IdBinder createIdBinder(BeanProperty[] uids) { + + if (uids.length == 0){ + // for report type beans that don't need an id + return EMPTY; + + } else if (uids.length == 1){ + if (uids[0].isEmbedded()){ + return new IdBinderEmbedded(idInExpandedForm, (BeanPropertyAssocOne)uids[0]); + } else { + return new IdBinderSimple(uids[0]); + } + + } else { + return new IdBinderMultiple(uids); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderMultiple.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderMultiple.java new file mode 100644 index 000000000..4556a968c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderMultiple.java @@ -0,0 +1,461 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.lib.util.MapFromString; +import com.avaje.ebeaninternal.server.type.DataBind; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.persistence.PersistenceException; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Bind an Id that is made up of multiple separate properties. + *

+ * The id passed in for binding is expected to be a map with the key being the + * String name of the property and the value being that properties bind value. + *

+ */ +public final class IdBinderMultiple implements IdBinder { + + private final BeanProperty[] props; + + private final String idProperties; + + private final String idInValueSql; + + public IdBinderMultiple(BeanProperty[] idProps) { + this.props = idProps; + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < idProps.length; i++) { + if (i > 0){ + sb.append(","); + } + sb.append(idProps[i].getName()); + } + idProperties = InternString.intern(sb.toString()); + + sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0){ + sb.append(","); + } + sb.append("?"); + } + sb.append(")"); + + idInValueSql = sb.toString(); + } + + public void initialise(){ + // do nothing + } + + public String getOrderBy(String pathPrefix, boolean ascending){ + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(" "); + } + if (pathPrefix != null){ + sb.append(pathPrefix).append("."); + } + sb.append(props[i].getName()); + if (!ascending){ + sb.append(" desc"); + } + } + return sb.toString(); + } + + public void createLdapNameById(LdapName name, Object id) throws InvalidNameException { + + if (id instanceof Map == false){ + throw new RuntimeException("Expecting a Map for concatinated key"); + } + + Map mapId = (Map)id; + for (int i = 0; i < props.length; i++) { + + Object v = mapId.get(props[i].getName()); + if (v == null){ + throw new RuntimeException("No value in Map for key "+props[i].getName()); + } + + Rdn rdn = new Rdn(props[i].getDbColumn(), v); + name.add(rdn); + } + } + + public void buildSelectExpressionChain(String prefix, List selectChain) { + + for (int i = 0; i < props.length; i++) { + props[i].buildSelectExpressionChain(prefix, selectChain); + } + } + + public void createLdapNameByBean(LdapName name, Object bean) throws InvalidNameException { + + for (int i = 0; i < props.length; i++) { + + Object v = props[i].getValue(bean); + Rdn rdn = new Rdn(props[i].getDbColumn(), v); + name.add(rdn); + } + } + + public int getPropertyCount() { + return props.length; + } + + public String getIdProperty() { + return idProperties; + } + + + public BeanProperty findBeanProperty(String dbColumnName) { + + for (int i = 0; i < props.length; i++) { + if (dbColumnName.equalsIgnoreCase(props[i].getDbColumn())){ + return props[i]; + } + } + + return null; + } + + public boolean isComplexId(){ + return true; + } + + public String getDefaultOrderBy() { + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0){ + sb.append(","); + } + + sb.append(props[i].getName()); + } + + return sb.toString(); + } + + public BeanProperty[] getProperties() { + return props; + } + + public void addIdInBindValue(SpiExpressionRequest request, Object value) { + for (int i = 0; i < props.length; i++) { + request.addBindValue(props[i].getValue(value)); + } + } + + public String getIdInValueExprDelete(int size) { + return getIdInValueExpr(size); + } + + public String getIdInValueExpr(int size) { + StringBuilder sb = new StringBuilder(); + sb.append(" in"); + sb.append(" ("); + sb.append(idInValueSql); + for (int i = 1; i < size; i++) { + sb.append(",").append(idInValueSql); + } + sb.append(") "); + return sb.toString(); + } + + public String getBindIdInSql(String baseTableAlias) { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(","); + } + if (baseTableAlias != null){ + sb.append(baseTableAlias); + sb.append("."); + } + sb.append(props[i].getDbColumn()); + } + sb.append(")"); + return sb.toString(); + } + + public Object[] getIdValues(Object bean){ + Object[] bindvalues = new Object[props.length]; + for (int i = 0; i < props.length; i++) { + bindvalues[i] = props[i].getValue(bean); + } + return bindvalues; + } + + @SuppressWarnings("unchecked") + public Object[] getBindValues(Object idValue){ + + Object[] bindvalues = new Object[props.length]; + // concatenated id as a Map + try { + Map uidMap = (Map) idValue; + + for (int i = 0; i < props.length; i++) { + Object value = uidMap.get(props[i].getName()); + bindvalues[i] = value; + } + + return bindvalues; + + } catch (ClassCastException e) { + String msg = "Expecting concatinated idValue to be a Map"; + throw new PersistenceException(msg, e); + } + } + + + public Object readTerm(String idTermValue) { + + String[] split = idTermValue.split("|"); + if (split.length != props.length){ + String msg = "Failed to split ["+idTermValue+"] using | for id."; + throw new PersistenceException(msg); + } + Map uidMap = new LinkedHashMap(); + for (int i = 0; i < props.length; i++) { + Object v = props[i].getScalarType().parse(split[i]); + uidMap.put(props[i].getName(), v); + } + return uidMap; + } + + @SuppressWarnings("unchecked") + public String writeTerm(Object idValue) { + + Map uidMap = (Map) idValue; + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < props.length; i++) { + Object v = uidMap.get(props[i].getName()); + String formatValue = props[i].getScalarType().format(v); + if (i > 0){ + sb.append("|"); + } + sb.append(formatValue); + } + return sb.toString(); + } + + public Object readData(DataInput dataInput) throws IOException { + + LinkedHashMap map = new LinkedHashMap(); + + boolean notNull = true; + + for (int i = 0; i < props.length; i++) { + Object value = props[i].readData(dataInput); + map.put(props[i].getName(), value); + if (value == null) { + notNull = false; + } + } + + if (notNull) { + return map; + } else { + return null; + } + } + + @SuppressWarnings("unchecked") + public void writeData(DataOutput dataOutput, Object idValue) throws IOException { + + Map map = (Map)idValue; + for (int i = 0; i < props.length; i++) { + Object embFieldValue = map.get(props[i].getName()); + //Object embFieldValue = props[i].getValue(idValue); + props[i].writeData(dataOutput, embFieldValue); + } + } + + public void loadIgnore(DbReadContext ctx) { + for (int i = 0; i < props.length; i++) { + props[i].loadIgnore(ctx); + } + } + + public Object readSet(DbReadContext ctx, Object bean) throws SQLException { + + LinkedHashMap map = new LinkedHashMap(); + boolean notNull = false; + for (int i = 0; i < props.length; i++) { + Object value = props[i].readSet(ctx, bean, null); + if (value != null){ + map.put(props[i].getName(), value); + notNull = true; + } + } + if (notNull){ + return map; + } else { + return null; + } + } + + public Object read(DbReadContext ctx) throws SQLException { + + LinkedHashMap map = new LinkedHashMap(); + boolean notNull = false; + for (int i = 0; i < props.length; i++) { + Object value = props[i].read(ctx); + if (value != null){ + map.put(props[i].getName(), value); + notNull = true; + } + } + if (notNull){ + return map; + } else { + return null; + } + } + + @SuppressWarnings("unchecked") + public void bindId(DefaultSqlUpdate sqlUpdate, Object idValue) { + // concatenated id as a Map + try { + Map uidMap = (Map) idValue; + + for (int i = 0; i < props.length; i++) { + Object value = uidMap.get(props[i].getName()); + sqlUpdate.addParameter(value); + } + + } catch (ClassCastException e) { + String msg = "Expecting concatinated idValue to be a Map"; + throw new PersistenceException(msg, e); + } + } + + @SuppressWarnings("unchecked") + public void bindId(DataBind bind, Object idValue) throws SQLException { + + // concatenated id as a Map + try { + Map uidMap = (Map) idValue; + + for (int i = 0; i < props.length; i++) { + Object value = uidMap.get(props[i].getName()); + props[i].bind(bind, value); + } + + } catch (ClassCastException e) { + String msg = "Expecting concatinated idValue to be a Map"; + throw new PersistenceException(msg, e); + } + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + for (int i = 0; i < props.length; i++) { + props[i].appendSelect(ctx, subQuery); + } + } + + public String getAssocIdInExpr(String prefix) { + + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(","); + } + if (prefix != null) { + sb.append(prefix); + sb.append("."); + } + sb.append(props[i].getName()); + } + sb.append(")"); + return sb.toString(); + } + + public String getAssocOneIdExpr(String prefix, String operator){ + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(" and "); + } + if (prefix != null){ + sb.append(prefix); + sb.append("."); + } + sb.append(props[i].getName()); + sb.append(operator); + } + return sb.toString(); + } + + public String getBindIdSql(String baseTableAlias) { + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + if (i > 0) { + sb.append(" and "); + } + if (baseTableAlias != null){ + sb.append(baseTableAlias); + sb.append("."); + } + sb.append(props[i].getDbColumn()); + sb.append(" = ? "); + } + return sb.toString(); + } + + public Object convertSetId(Object idValue, Object bean) { + + // allow Map or String for concatenated id + Map mapVal = null; + if (idValue instanceof Map) { + mapVal = (Map) idValue; + } else { + mapVal = MapFromString.parse(idValue.toString()); + } + + // Use a new LinkedHashMap to control the order + LinkedHashMap newMap = new LinkedHashMap(); + + for (int i = 0; i < props.length; i++) { + BeanProperty prop = props[i]; + + Object value = mapVal.get(prop.getName()); + + // Convert the property type if required + value = props[i].getScalarType().toBeanType(value); + newMap.put(prop.getName(), value); + if (bean != null) { + // support PropertyChangeSupport + prop.setValueIntercept(bean, value); + } + } + + return newMap; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderSimple.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderSimple.java new file mode 100644 index 000000000..172903c9e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/IdBinderSimple.java @@ -0,0 +1,233 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.ScalarType; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +/** + * Bind an Id where the Id is made of a single property (not embedded). + */ +public final class IdBinderSimple implements IdBinder { + + private final BeanProperty idProperty; + + private final String bindIdSql; + + private final BeanProperty[] properties; + + private final Class expectedType; + + @SuppressWarnings("rawtypes") + private final ScalarType scalarType; + + public IdBinderSimple(BeanProperty idProperty) { + this.idProperty = idProperty; + this.scalarType = idProperty.getScalarType(); + this.expectedType = idProperty.getPropertyType(); + this.properties = new BeanProperty[1]; + properties[0] = idProperty; + bindIdSql = InternString.intern(idProperty.getDbColumn()+" = ? "); + } + + public void initialise(){ + // do nothing + } + + public Object readTerm(String idTermValue) { + return scalarType.parse(idTermValue); + } + + public String writeTerm(Object idValue) { + return scalarType.format(idValue); + } + + public String getOrderBy(String pathPrefix, boolean ascending){ + + StringBuilder sb = new StringBuilder(); + if (pathPrefix != null){ + sb.append(pathPrefix).append("."); + } + sb.append(idProperty.getName()); + if (!ascending){ + sb.append(" desc"); + } + return sb.toString(); + } + + public void buildSelectExpressionChain(String prefix, List selectChain) { + + idProperty.buildSelectExpressionChain(prefix, selectChain); + } + + public void createLdapNameById(LdapName name, Object id) throws InvalidNameException { + Rdn rdn = new Rdn(idProperty.getDbColumn(), id); + name.add(rdn); + } + + public void createLdapNameByBean(LdapName name, Object bean) throws InvalidNameException { + Object id = idProperty.getValue(bean); + createLdapNameById(name, id); + } + + /** + * Returns 1. + */ + public int getPropertyCount() { + return 1; + } + + public String getIdProperty() { + return idProperty.getName(); + } + + public BeanProperty findBeanProperty(String dbColumnName) { + if (dbColumnName.equalsIgnoreCase(idProperty.getDbColumn())){ + return idProperty; + } + return null; + } + + public boolean isComplexId(){ + return false; + } + + public String getDefaultOrderBy() { + return idProperty.getName(); + } + + public BeanProperty[] getProperties() { + return properties; + } + + public String getBindIdInSql(String baseTableAlias) { + if (baseTableAlias == null){ + return idProperty.getDbColumn(); + } else { + return baseTableAlias+"."+idProperty.getDbColumn(); + } + } + + public String getBindIdSql(String baseTableAlias) { + if (baseTableAlias == null){ + return bindIdSql; + } else { + return baseTableAlias+"."+bindIdSql; + } + } + + public Object[] getIdValues(Object bean){ + return new Object[]{idProperty.getValue(bean)}; + } + + public Object[] getBindValues(Object idValue){ + return new Object[]{idValue}; + } + + public String getIdInValueExprDelete(int size) { + return getIdInValueExpr(size); + } + + public String getIdInValueExpr(int size) { + StringBuilder sb = new StringBuilder(2*size+10); + sb.append(" in"); + sb.append(" (?"); + for (int i = 1; i < size; i++) { + sb.append(",?"); + } + sb.append(") "); + return sb.toString(); + } + + public void addIdInBindValue(SpiExpressionRequest request, Object value) { + value = convertSetId(value, null); + request.addBindValue(value); + } + + public void bindId(DefaultSqlUpdate sqlUpdate, Object value) { + sqlUpdate.addParameter(value); + } + + public void bindId(DataBind dataBind, Object value) throws SQLException { + value = idProperty.toBeanType(value); + idProperty.bind(dataBind, value); + } + + public void writeData(DataOutput os, Object value) throws IOException { + idProperty.writeData(os, value); + } + + public Object readData(DataInput is) throws IOException { + return idProperty.readData(is); + } + + public void loadIgnore(DbReadContext ctx) { + idProperty.loadIgnore(ctx); + } + + public Object readSet(DbReadContext ctx, Object bean) throws SQLException { + Object id = idProperty.read(ctx); + if (id != null){ + idProperty.setValue(bean, id); + } + return id; + } + + public Object read(DbReadContext ctx) throws SQLException { + return idProperty.read(ctx); + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + idProperty.appendSelect(ctx, subQuery); + } + + public String getAssocOneIdExpr(String prefix, String operator){ + + StringBuilder sb = new StringBuilder(); + if (prefix != null){ + sb.append(prefix); + sb.append("."); + } + sb.append(idProperty.getName()); + sb.append(operator); + return sb.toString(); + } + + public String getAssocIdInExpr(String prefix) { + + StringBuilder sb = new StringBuilder(); + if (prefix != null) { + sb.append(prefix); + sb.append("."); + } + sb.append(idProperty.getName()); + return sb.toString(); + } + + public Object convertSetId(Object idValue, Object bean) { + + if (!idValue.getClass().equals(expectedType)){ + idValue = scalarType.toBeanType(idValue); + } + + if (bean != null) { + // support PropertyChangeSupport + idProperty.setValueIntercept(bean, idValue); + } + + return idValue; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedId.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedId.java new file mode 100644 index 000000000..dc6a9dde6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedId.java @@ -0,0 +1,72 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import java.sql.SQLException; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.IntersectionRow; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest; + +/** + * Represents a imported property. + */ +public interface ImportedId { + + public void addFkeys(String name); + + /** + * Return true if this id is a simple single scalar value. False if it is a + * compound id (embedded or multiple). + */ + public boolean isScalar(); + + /** + * Return the logical property name. + */ + public String getLogicalName(); + + /** + * For scalar id return the related single db column. + *

+ * This is essentially the imported foreign key column (where there is only + * one). + *

+ */ + public String getDbColumn(); + + /** + * Append the the SQL query statement. + */ + public void sqlAppend(DbSqlContext ctx); + + /** + * Append to the DML statement. + */ + public void dmlAppend(GenerateDmlRequest request); + + /** + * Append to the DML statement to the where clause. + */ + public void dmlWhere(GenerateDmlRequest request, Object bean); + + /** + * Return true if the id value has changed. + */ + public boolean hasChanged(Object bean, Object oldValues); + + /** + * Bind the value from the bean. + */ + public Object bind(BindableRequest request, Object bean, boolean bindNull) throws SQLException; + + /** + * For inserting into ManyToMany intersection. + */ + public void buildImport(IntersectionRow row, Object other); + + /** + * Used to derive a missing concatenated key from multiple imported keys. + */ + public BeanProperty findMatchImport(String matchDbColumn); +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java new file mode 100644 index 000000000..f240a7c7b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java @@ -0,0 +1,164 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.deploy.BeanFkeyProperty; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.IntersectionRow; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest; +import com.avaje.ebeaninternal.util.ValueUtil; + +/** + * Imported Embedded id. + */ +public class ImportedIdEmbedded implements ImportedId { + + final BeanPropertyAssoc owner; + + final BeanPropertyAssocOne foreignAssocOne; + + final ImportedIdSimple[] imported; + + public ImportedIdEmbedded(BeanPropertyAssoc owner, BeanPropertyAssocOne foreignAssocOne, ImportedIdSimple[] imported) { + this.owner = owner; + this.foreignAssocOne = foreignAssocOne; + this.imported = imported; + } + + public void addFkeys(String name) { + + BeanProperty[] embeddedProps = foreignAssocOne.getProperties(); + + for (int i = 0; i < imported.length; i++) { + String n = name+"."+foreignAssocOne.getName()+"."+embeddedProps[i].getName(); + BeanFkeyProperty fkey = new BeanFkeyProperty(null, n, imported[i].localDbColumn, foreignAssocOne.getDeployOrder()); + owner.getBeanDescriptor().add(fkey); + } + } + + public boolean isScalar(){ + return false; + } + + public String getLogicalName() { + return owner.getName()+"."+foreignAssocOne.getName(); + } + + + public String getDbColumn(){ + return null; + } + + public void sqlAppend(DbSqlContext ctx) { + for (int i = 0; i < imported.length; i++) { + ctx.appendColumn(imported[i].localDbColumn); + } + } + + public void dmlAppend(GenerateDmlRequest request) { + + for (int i = 0; i < imported.length; i++) { + request.appendColumn(imported[i].localDbColumn); + } + } + + public void dmlWhere(GenerateDmlRequest request, Object bean){ + + Object embeddedId = null; + if (bean != null) { + embeddedId = foreignAssocOne.getValue(bean); + } + + if (embeddedId == null){ + for (int i = 0; i < imported.length; i++) { + if (imported[i].owner.isDbUpdatable()) { + request.appendColumnIsNull(imported[i].localDbColumn); + } + } + } else { + + for (int i = 0; i < imported.length; i++) { + if (imported[i].owner.isDbUpdatable()) { + Object value = imported[i].foreignProperty.getValue(embeddedId); + if (value == null){ + request.appendColumnIsNull(imported[i].localDbColumn); + } else { + request.appendColumn(imported[i].localDbColumn); + } + } + } + } + } + + public boolean hasChanged(Object bean, Object oldValues) { + Object id = foreignAssocOne.getValue(bean); + Object oldId = foreignAssocOne.getValue(oldValues); + + return !ValueUtil.areEqual(id, oldId); + } + + public Object bind(BindableRequest request, Object bean, boolean bindNull) throws SQLException { + + Object embeddedId = null; + + if (bean != null) { + embeddedId = foreignAssocOne.getValue(bean); + } + + if (embeddedId == null){ + for (int i = 0; i < imported.length; i++) { + if (imported[i].owner.isUpdateable()) { + request.bind(null, imported[i].foreignProperty, imported[i].localDbColumn, true); + } + } + + } else { + for (int i = 0; i < imported.length; i++) { + if (imported[i].owner.isUpdateable()) { + Object scalarValue = imported[i].foreignProperty.getValue(embeddedId); + request.bind(scalarValue, imported[i].foreignProperty, imported[i].localDbColumn, true); + } + } + } + // hmmm, not worrying about this just yet + return null; + } + + public void buildImport(IntersectionRow row, Object other){ + + Object embeddedId = foreignAssocOne.getValue(other); + if (embeddedId == null){ + String msg = "Foreign Key value null?"; + throw new PersistenceException(msg); + } + + for (int i = 0; i < imported.length; i++) { + Object scalarValue = imported[i].foreignProperty.getValue(embeddedId); + row.put(imported[i].localDbColumn, scalarValue); + } + + } + + /** + * Not supported for embedded id. + */ + public BeanProperty findMatchImport(String matchDbColumn) { + + BeanProperty p = null; + for (int i = 0; i < imported.length; i++) { + p = imported[i].findMatchImport(matchDbColumn); + if (p != null){ + return p; + } + } + + return p; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdMultiple.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdMultiple.java new file mode 100644 index 000000000..c4679f010 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdMultiple.java @@ -0,0 +1,123 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import java.sql.SQLException; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.IntersectionRow; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest; +import com.avaje.ebeaninternal.util.ValueUtil; + +/** + * Imported concatenated id that is not embedded. + */ +public class ImportedIdMultiple implements ImportedId { + + final BeanPropertyAssoc owner; + + final ImportedIdSimple[] imported; + + public ImportedIdMultiple(BeanPropertyAssoc owner, ImportedIdSimple[] imported) { + this.owner = owner; + this.imported = imported; + } + + public void addFkeys(String name) { + + // not supporting addFkeys for ImportedIdMultiple + } + + public String getLogicalName() { + return null; + } + + public boolean isScalar(){ + return false; + } + + public String getDbColumn(){ + return null; + } + + + public void sqlAppend(DbSqlContext ctx) { + for (int i = 0; i < imported.length; i++) { + ctx.appendColumn(imported[i].localDbColumn); + } + } + + public void dmlAppend(GenerateDmlRequest request) { + + for (int i = 0; i < imported.length; i++) { + request.appendColumn(imported[i].localDbColumn); + } + } + + public void dmlWhere(GenerateDmlRequest request, Object bean){ + if (bean == null){ + for (int i = 0; i < imported.length; i++) { + request.appendColumnIsNull(imported[i].localDbColumn); + } + } else { + for (int i = 0; i < imported.length; i++) { + Object value = imported[i].foreignProperty.getValue(bean); + if (value == null){ + request.appendColumnIsNull(imported[i].localDbColumn); + } else { + request.appendColumn(imported[i].localDbColumn); + } + } + } + } + + public boolean hasChanged(Object bean, Object oldValues) { + + for (int i = 0; i < imported.length; i++) { + Object id = imported[i].foreignProperty.getValue(bean); + Object oldId = imported[i].foreignProperty.getValue(oldValues); + if (!ValueUtil.areEqual(id, oldId)) { + return true; + } + } + return false; + } + + + public Object bind(BindableRequest request, Object bean, boolean bindNull) throws SQLException { + + for (int i = 0; i < imported.length; i++) { + if (imported[i].owner.isUpdateable()) { + Object scalarValue = imported[i].foreignProperty.getValue(bean); + request.bind(scalarValue, imported[i].foreignProperty, imported[i].localDbColumn, true); + } + } + // hmmm, not worrying about this just yet + return null; + } + + public void buildImport(IntersectionRow row, Object other){ + + for (int i = 0; i < imported.length; i++) { + Object scalarValue = imported[i].foreignProperty.getValue(other); + row.put(imported[i].localDbColumn, scalarValue); + } + } + + /** + * Not supported for concatenated id. + */ + public BeanProperty findMatchImport(String matchDbColumn) { + + BeanProperty p = null; + for (int i = 0; i < imported.length; i++) { + p = imported[i].findMatchImport(matchDbColumn); + if (p != null){ + return p; + } + } + + return p; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java new file mode 100644 index 000000000..ad21b2631 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/ImportedIdSimple.java @@ -0,0 +1,161 @@ +package com.avaje.ebeaninternal.server.deploy.id; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.BeanFkeyProperty; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.IntersectionRow; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest; +import com.avaje.ebeaninternal.util.ValueUtil; + +/** + * Single scalar imported id. + */ +public final class ImportedIdSimple implements ImportedId, Comparable { + + /** + * Helper class to sort ImportedIdSimple. + */ + private final static class EntryComparator implements Comparator { + public int compare(ImportedIdSimple o1, ImportedIdSimple o2) { + return o1.compareTo(o2); + } + } + + private static final EntryComparator COMPARATOR = new EntryComparator(); + + protected final BeanPropertyAssoc owner; + + protected final String localDbColumn; + + protected final String logicalName; + + protected final BeanProperty foreignProperty; + + protected final int position; + + public ImportedIdSimple(BeanPropertyAssoc owner, String localDbColumn, BeanProperty foreignProperty, int position) { + this.owner = owner; + this.localDbColumn = InternString.intern(localDbColumn); + this.foreignProperty = foreignProperty; + this.position = position; + this.logicalName = InternString.intern(owner.getName()+"."+foreignProperty.getName()); + } + + /** + * Return the list as an array sorted into the same order as the Bean Properties. + */ + public static ImportedIdSimple[] sort(List list) { + + ImportedIdSimple[] importedIds = (ImportedIdSimple[]) list.toArray(new ImportedIdSimple[list.size()]); + + // sort into the same order as the BeanProperties + Arrays.sort(importedIds, COMPARATOR); + return importedIds; + } + + @Override + public boolean equals(Object obj) { + // remove FindBugs warning + return obj == this; + } + + public int compareTo(ImportedIdSimple other) { + return (position < other.position ? -1 : (position == other.position ? 0 : 1)); + } + + public void addFkeys(String name) { + BeanFkeyProperty fkey = new BeanFkeyProperty(null, name+"."+foreignProperty.getName(), localDbColumn, owner.getDeployOrder()); + owner.getBeanDescriptor().add(fkey); + } + + public boolean isScalar(){ + return true; + } + + public String getLogicalName() { + return logicalName; + } + + public String getDbColumn(){ + return localDbColumn; + } + + private Object getIdValue(Object bean) { + return foreignProperty.getValueWithInheritance(bean); + } + + public void buildImport(IntersectionRow row, Object other){ + + Object value = getIdValue(other); + if (value == null){ + String msg = "Foreign Key value null?"; + throw new PersistenceException(msg); + } + + row.put(localDbColumn, value); + } + + public void sqlAppend(DbSqlContext ctx) { + ctx.appendColumn(localDbColumn); + } + + + public void dmlAppend(GenerateDmlRequest request) { + request.appendColumn(localDbColumn); + } + + public void dmlWhere(GenerateDmlRequest request, Object bean){ + + if (owner.isDbUpdatable()){ + Object value = null; + if (bean != null){ + value = getIdValue(bean); + } + if (value == null){ + request.appendColumnIsNull(localDbColumn); + } else { + request.appendColumn(localDbColumn); + } + } + } + + public boolean hasChanged(Object bean, Object oldValues) { + + Object id = getIdValue(bean); + + if (oldValues != null){ + Object oldId = getIdValue(oldValues); + return !ValueUtil.areEqual(id, oldId); + } + + return true; + } + + public Object bind(BindableRequest request, Object bean, boolean bindNull) throws SQLException { + + Object value = null; + if (bean != null){ + value = getIdValue(bean); + } + request.bind(value, foreignProperty, localDbColumn, bindNull); + return value; + } + + public BeanProperty findMatchImport(String matchDbColumn) { + + if (matchDbColumn.equals(localDbColumn)) { + return foreignProperty; + } + return null; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/id/package.html b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/package.html new file mode 100644 index 000000000..fe4d3a6f3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/id/package.html @@ -0,0 +1,10 @@ + + + + Helpers for Id property conversion + + +Helpers for Id property conversion + + + \ No newline at end of file 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 new file mode 100644 index 000000000..7d3624a36 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -0,0 +1,1013 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.Query.UseIndex; +import com.avaje.ebean.config.TableName; +import com.avaje.ebean.config.dbplatform.IdGenerator; +import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.meta.MetaAutoFetchStatistic; +import com.avaje.ebeaninternal.server.core.CacheOptions; +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; +import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistController; +import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistListener; +import com.avaje.ebeaninternal.server.deploy.ChainedBeanQueryAdapter; +import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint; +import com.avaje.ebeaninternal.server.deploy.DRawSqlMeta; +import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; +import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.reflect.BeanReflect; + +/** + * Describes Beans including their deployment information. + */ +public class DeployBeanDescriptor { + + static class PropOrder implements Comparator { + + public int compare(DeployBeanProperty o1, DeployBeanProperty o2) { + + int v2 = o1.getSortOrder(); + int v1 = o2.getSortOrder(); + return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1)); + } + } + + private static final PropOrder PROP_ORDER = new PropOrder(); + + private static final String I_SCALAOBJECT = "scala.ScalaObject"; + + private static final Logger logger = Logger.getLogger(DeployBeanDescriptor.class.getName()); + + private static final String META_BEAN_PREFIX = MetaAutoFetchStatistic.class.getName().substring(0, 20); + + /** + * Map of BeanProperty Linked so as to preserve order. + */ + private LinkedHashMap propMap = new LinkedHashMap(); + + /** + * The type of bean this describes. + */ + private final Class beanType; + + private EntityType entityType; + + private final Map namedQueries = new LinkedHashMap(); + + private final Map namedUpdates = new LinkedHashMap(); + + private final Map rawSqlMetas = new LinkedHashMap(); + + private DeployBeanPropertyAssocOne unidirectional; + + /** + * Type of Identity generation strategy used. + */ + private IdType idType; + + /** + * The name of an IdGenerator (optional). + */ + private String idGeneratorName; + + private IdGenerator idGenerator; + + /** + * The database sequence name (optional). + */ + private String sequenceName; + + private String ldapBaseDn; + + private String[] ldapObjectclasses; + + /** + * Used with Identity columns but no getGeneratedKeys support. + */ + private String selectLastInsertedId; + + private String lazyFetchIncludes; + + /** + * The concurrency mode for beans of this type. + */ + private ConcurrencyMode concurrencyMode = ConcurrencyMode.ALL; + + private boolean updateChangesOnly; + + /** + * The tables this bean is dependent on. + */ + private String[] dependantTables; + + private List compoundUniqueConstraints; + + /** + * Extra deployment attributes. + */ + private HashMap extraAttrMap = new HashMap(); + + /** + * The base database table. + */ + private String baseTable; + private TableName baseTableFull; + + /** + * Used to provide mechanism to new EntityBean instances. Generated code + * faster than reflection at this stage. + */ + private BeanReflect beanReflect; + + /** + * The EntityBean type used to create new EntityBeans. + */ + private Class factoryType; + + private List persistControllers = new ArrayList(); + private List> persistListeners = new ArrayList>(); + private List queryAdapters = new ArrayList(); + + private CacheOptions cacheOptions = new CacheOptions(); + + /** + * If set overrides the find implementation. Server side only. + */ + private BeanFinder beanFinder; + + private UseIndex useIndex; + + /** + * The table joins for this bean. Server side only. + */ + private ArrayList tableJoinList = new ArrayList(); + + /** + * Inheritance information. Server side only. + */ + private InheritInfo inheritInfo; + + private String name; + + private boolean processedRawSqlExtend; + + /** + * Construct the BeanDescriptor. + */ + public DeployBeanDescriptor(Class beanType) { + this.beanType = beanType; + } + + /** + * Return true if this beanType is an abstract class. + */ + public boolean isAbstract() { + return Modifier.isAbstract(beanType.getModifiers()); + } + + /** + * Return the default UseIndex strategy. + */ + public UseIndex getUseIndex() { + return useIndex; + } + + /** + * Set the default UseIndex strategy. + */ + public void setUseIndex(UseIndex useIndex) { + this.useIndex = useIndex; + } + + public boolean isScalaObject() { + Class[] interfaces = beanType.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + String iname = interfaces[i].getName(); + if (I_SCALAOBJECT.equals(iname)) { + return true; + } + } + return false; + } + + public Collection getRawSqlMeta() { + if (!processedRawSqlExtend) { + rawSqlProcessExtend(); + processedRawSqlExtend = true; + } + return rawSqlMetas.values(); + } + + /** + * Process the "extend" attributes of raw SQL. Aka inherit the query and + * column mapping. + */ + private void rawSqlProcessExtend() { + + for (DRawSqlMeta rawSqlMeta : rawSqlMetas.values()) { + String extend = rawSqlMeta.getExtend(); + if (extend != null) { + DRawSqlMeta parentQuery = rawSqlMetas.get(extend); + if (parentQuery == null) { + throw new RuntimeException("parent query [" + extend + "] not found for sql-select " + rawSqlMeta.getName()); + } + rawSqlMeta.extend(parentQuery); + } + } + } + + public DeployBeanTable createDeployBeanTable() { + + DeployBeanTable beanTable = new DeployBeanTable(getBeanType()); + beanTable.setBaseTable(baseTable); + beanTable.setIdProperties(propertiesId()); + + return beanTable; + } + + /** + * Check all the properties to see if they all have read and write methods + * (required if using "subclassing" but not for "enhancement"). + */ + public boolean checkReadAndWriteMethods() { + + if (isMeta()) { + return true; + } + boolean missingMethods = false; + + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (!prop.isTransient()) { + String m = ""; + if (prop.getReadMethod() == null) { + m += " missing readMethod "; + } + if (prop.getWriteMethod() == null) { + m += " missing writeMethod "; + } + if (!"".equals(m)) { + m += ". Should it be transient?"; + String msg = "Bean property " + getFullName() + "." + prop.getName() + " has " + m; + logger.log(Level.SEVERE, msg); + missingMethods = true; + } + } + } + return !missingMethods; + } + + public void setEntityType(EntityType entityType) { + this.entityType = entityType; + } + + public boolean isEmbedded() { + return EntityType.EMBEDDED.equals(entityType); + } + + public boolean isBaseTableType() { + EntityType et = getEntityType(); + return EntityType.ORM.equals(et); + } + + public EntityType getEntityType() { + if (entityType == null) { + entityType = isMeta() ? EntityType.META : EntityType.ORM; + } + return entityType; + } + + /** + * Return true if this is a Meta entity bean. + *

+ * The Meta entity beans are not based on real tables but get meta information + * from memory such as all the entity bean meta data. + *

+ */ + private boolean isMeta() { + return beanType.getName().startsWith(META_BEAN_PREFIX); + } + + public void add(DRawSqlMeta rawSqlMeta) { + rawSqlMetas.put(rawSqlMeta.getName(), rawSqlMeta); + if ("default".equals(rawSqlMeta.getName())) { + setEntityType(EntityType.SQL); + } + } + + public void add(DeployNamedUpdate namedUpdate) { + namedUpdates.put(namedUpdate.getName(), namedUpdate); + } + + public void add(DeployNamedQuery namedQuery) { + namedQueries.put(namedQuery.getName(), namedQuery); + if ("default".equals(namedQuery.getName())) { + setEntityType(EntityType.SQL); + } + } + + public Map getNamedQueries() { + return namedQueries; + } + + public Map getNamedUpdates() { + return namedUpdates; + } + + public BeanReflect getBeanReflect() { + return beanReflect; + } + + /** + * Return the class type this BeanDescriptor describes. + */ + public Class getBeanType() { + return beanType; + } + + /** + * Return the class type this BeanDescriptor describes. + */ + public Class getFactoryType() { + return factoryType; + } + + /** + * Set the class used to create new EntityBean instances. + *

+ * Normally this would be a subclass dynamically generated for this bean. + *

+ */ + public void setFactoryType(Class factoryType) { + this.factoryType = factoryType; + } + + /** + * Set the BeanReflect used to create new instances of an EntityBean. This + * could use reflection or code generation to do this. + */ + public void setBeanReflect(BeanReflect beanReflect) { + this.beanReflect = beanReflect; + } + + /** + * Returns the Inheritance mapping information. This will be null if this type + * of bean is not involved in any ORM inheritance mapping. + */ + public InheritInfo getInheritInfo() { + return inheritInfo; + } + + /** + * Set the ORM inheritance mapping information. + */ + public void setInheritInfo(InheritInfo inheritInfo) { + this.inheritInfo = inheritInfo; + } + + /** + * Return the reference options. + */ + public CacheOptions getCacheOptions() { + return cacheOptions; + } + + public boolean isNaturalKeyProperty(String name) { + return name.equals(cacheOptions.getNaturalKey()); + } + + public DeployBeanPropertyAssocOne getUnidirectional() { + return unidirectional; + } + + public void setUnidirectional(DeployBeanPropertyAssocOne unidirectional) { + this.unidirectional = unidirectional; + } + + /** + * Return the concurrency mode used for beans of this type. + */ + public ConcurrencyMode getConcurrencyMode() { + return concurrencyMode; + } + + /** + * Set the concurrency mode used for beans of this type. + */ + public void setConcurrencyMode(ConcurrencyMode concurrencyMode) { + this.concurrencyMode = concurrencyMode; + } + + public String getLdapBaseDn() { + return ldapBaseDn; + } + + public void setLdapBaseDn(String ldapBaseDn) { + this.ldapBaseDn = ldapBaseDn; + } + + public String[] getLdapObjectclasses() { + return ldapObjectclasses; + } + + public void setLdapObjectclasses(String[] ldapObjectclasses) { + this.ldapObjectclasses = ldapObjectclasses; + } + + public boolean isUpdateChangesOnly() { + return updateChangesOnly; + } + + public void setUpdateChangesOnly(boolean updateChangesOnly) { + this.updateChangesOnly = updateChangesOnly; + } + + /** + * Return the tables this bean is dependant on. This implies that if any of + * these tables are modified then cached beans may be invalidated. + */ + public String[] getDependantTables() { + return dependantTables; + } + + /** + * Add a compound unique constraint. + */ + public void addCompoundUniqueConstraint(CompoundUniqueContraint c) { + if (compoundUniqueConstraints == null) { + compoundUniqueConstraints = new ArrayList(); + } + compoundUniqueConstraints.add(c); + } + + /** + * Return the compound unique constraints (can be null). + */ + public CompoundUniqueContraint[] getCompoundUniqueConstraints() { + if (compoundUniqueConstraints == null) { + return null; + } else { + return compoundUniqueConstraints.toArray(new CompoundUniqueContraint[compoundUniqueConstraints.size()]); + } + } + + /** + * Set the tables this bean is dependant on. This implies that if any of these + * tables are modified then cached beans may be invalidated. + */ + public void setDependantTables(String[] dependantTables) { + this.dependantTables = dependantTables; + } + + /** + * Return the beanFinder. Usually null unless overriding the finder. + */ + public BeanFinder getBeanFinder() { + return beanFinder; + } + + /** + * Set the BeanFinder to use for beans of this type. This is set to override + * the finding from the default. + */ + public void setBeanFinder(BeanFinder beanFinder) { + this.beanFinder = beanFinder; + } + + /** + * Return the BeanPersistController (could be a chain of them, 1 or null). + */ + public BeanPersistController getPersistController() { + if (persistControllers.size() == 0) { + return null; + } else if (persistControllers.size() == 1) { + return persistControllers.get(0); + } else { + return new ChainedBeanPersistController(persistControllers); + } + } + + /** + * Return the BeanPersistListener (could be a chain of them, 1 or null). + */ + public BeanPersistListener getPersistListener() { + if (persistListeners.size() == 0) { + return null; + } else if (persistListeners.size() == 1) { + return persistListeners.get(0); + } else { + return new ChainedBeanPersistListener(persistListeners); + } + } + + public BeanQueryAdapter getQueryAdapter() { + if (queryAdapters.size() == 0) { + return null; + } else if (queryAdapters.size() == 1) { + return queryAdapters.get(0); + } else { + return new ChainedBeanQueryAdapter(queryAdapters); + } + } + + /** + * Set the Controller. + */ + public void addPersistController(BeanPersistController controller) { + persistControllers.add(controller); + } + + public void addPersistListener(BeanPersistListener listener) { + persistListeners.add(listener); + } + + public void addQueryAdapter(BeanQueryAdapter queryAdapter) { + queryAdapters.add(queryAdapter); + } + + /** + * Return true if this bean type should use IdGeneration. + *

+ * If this is false and the Id is null it is assumed that a database auto + * increment feature is being used to populate the id. + *

+ */ + public boolean isUseIdGenerator() { + return idType == IdType.GENERATOR; + } + + /** + * Return the base table. Only properties mapped to the base table are by + * default persisted. + */ + public String getBaseTable() { + return baseTable; + } + + /** + * Return the base table with full structure. + */ + public TableName getBaseTableFull() { + return baseTableFull; + } + + /** + * Set the base table. Only properties mapped to the base table are by default + * persisted. + */ + public void setBaseTable(TableName baseTableFull) { + this.baseTableFull = baseTableFull; + this.baseTable = baseTableFull == null ? null : baseTableFull.getQualifiedName(); + } + + public void sortProperties() { + + ArrayList list = new ArrayList(); + list.addAll(propMap.values()); + + Collections.sort(list, PROP_ORDER); + + propMap = new LinkedHashMap(list.size()); + for (int i = 0; i < list.size(); i++) { + addBeanProperty(list.get(i)); + } + } + + /** + * Add a bean property. + */ + public DeployBeanProperty addBeanProperty(DeployBeanProperty prop) { + return propMap.put(prop.getName(), prop); + } + + /** + * Get a BeanProperty by its name. + */ + public DeployBeanProperty getBeanProperty(String propName) { + return propMap.get(propName); + } + + public Map getExtraAttributeMap() { + return extraAttrMap; + } + + /** + * Get a named extra attribute. + */ + public String getExtraAttribute(String key) { + return (String) extraAttrMap.get(key); + } + + /** + * Set an extra attribute with a given name. + * + * @param key + * the name of the extra attribute + * @param value + * the value of the extra attribute + */ + public void setExtraAttribute(String key, String value) { + extraAttrMap.put(key, value); + } + + /** + * Return the bean class name this descriptor is used for. + *

+ * If this BeanDescriptor is for a table then this returns the table name + * instead. + *

+ */ + public String getFullName() { + return beanType.getName(); + } + + /** + * Return the bean short name. + */ + public String getName() { + return name; + } + + /** + * Set the bean shortName. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Return the identity generation type. + */ + public IdType getIdType() { + return idType; + } + + /** + * Set the identity generation type. + */ + public void setIdType(IdType idType) { + this.idType = idType; + } + + /** + * Return the DB sequence name (can be null). + */ + public String getSequenceName() { + return sequenceName; + } + + /** + * Set the DB sequence name. + */ + public void setSequenceName(String sequenceName) { + this.sequenceName = sequenceName; + } + + /** + * Return the SQL used to return the last inserted Id. + *

+ * Used with Identity columns where getGeneratedKeys is not supported. + *

+ */ + public String getSelectLastInsertedId() { + return selectLastInsertedId; + } + + /** + * Set the SQL used to return the last inserted Id. + */ + public void setSelectLastInsertedId(String selectLastInsertedId) { + this.selectLastInsertedId = selectLastInsertedId; + } + + /** + * Return the name of the IdGenerator that should be used with this type of + * bean. A null value could be used to specify the 'default' IdGenerator. + */ + public String getIdGeneratorName() { + return idGeneratorName; + } + + /** + * Set the name of the IdGenerator that should be used with this type of bean. + */ + public void setIdGeneratorName(String idGeneratorName) { + this.idGeneratorName = idGeneratorName; + } + + /** + * Return the actual IdGenerator for this bean type (can be null). + */ + public IdGenerator getIdGenerator() { + return idGenerator; + } + + /** + * Set the actual IdGenerator for this bean type. + */ + public void setIdGenerator(IdGenerator idGenerator) { + this.idGenerator = idGenerator; + if (idGenerator != null && idGenerator.isDbSequence()) { + setSequenceName(idGenerator.getName()); + } + } + + /** + * Return the includes for getReference(). + */ + public String getLazyFetchIncludes() { + return lazyFetchIncludes; + } + + /** + * Set includes to use for lazy loading by getReference(). Note queries also + * build references and includes on the actual association are used for those + * references. + */ + public void setLazyFetchIncludes(String lazyFetchIncludes) { + if (lazyFetchIncludes != null && lazyFetchIncludes.length() > 0) { + this.lazyFetchIncludes = lazyFetchIncludes; + } + } + + /** + * Summary description. + */ + public String toString() { + return getFullName(); + } + + /** + * Add a TableJoin to this type of bean. For Secondary table properties. + */ + public void addTableJoin(DeployTableJoin join) { + tableJoinList.add(join); + } + + public List getTableJoins() { + return tableJoinList; + } + + /** + * Return an Iterator of all BeanProperty. + */ + public Iterator propertiesAll() { + return propMap.values().iterator(); + } + + /** + * Return the defaultSelectClause using FetchType.LAZY and FetchType.EAGER. + */ + public String getDefaultSelectClause() { + + StringBuilder sb = new StringBuilder(); + + boolean hasLazyFetch = false; + + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop.isTransient()) { + // ignore transient props etc + } else if (prop instanceof DeployBeanPropertyAssocMany) { + // ignore the associated many properties + } else { + if (prop.isFetchEager()) { + sb.append(prop.getName()).append(","); + } else { + hasLazyFetch = true; + } + } + } + + if (!hasLazyFetch) { + return null; + } + String selectClause = sb.toString(); + return selectClause.substring(0, selectClause.length() - 1); + } + + /** + * Return an Array of properties to include in default fetch (for LDAP). + */ + public String[] getDefaultSelectDbArray(Set defaultSelect) { + + ArrayList list = new ArrayList(); + for (DeployBeanProperty p : propMap.values()) { + if (defaultSelect != null) { + if (defaultSelect.contains(p.getName())) { + // properties in defaultSelect + list.add(p.getDbColumn()); + } + } else if (!p.isTransient() && p.isDbRead()) { + // non transient db properties + list.add(p.getDbColumn()); + } + } + return list.toArray(new String[list.size()]); + } + + /** + * Parse the include separating by comma or semicolon. + */ + public Set parseDefaultSelectClause(String rawList) { + + if (rawList == null) { + return null; + } + + String[] res = rawList.split(","); + + LinkedHashSet set = new LinkedHashSet(res.length + 3); + + String temp = null; + for (int i = 0; i < res.length; i++) { + temp = res[i].trim(); + if (temp.length() > 0) { + set.add(temp); + } + } + return Collections.unmodifiableSet(set); + } + + /** + * Return the Primary Key column assuming it is a single column (not + * compound). This is for the purpose of defining a sequence name. + */ + public String getSinglePrimaryKeyColumn() { + List ids = propertiesId(); + if (ids.size() == 1) { + DeployBeanProperty p = ids.get(0); + if (p instanceof DeployBeanPropertyAssoc) { + // its a compound primary key + return null; + } else { + return p.getDbColumn(); + } + } + return null; + } + + /** + * Return the BeanProperty that make up the unique id. + *

+ * The order of these properties can be relied on to be consistent if the bean + * itself doesn't change or the xml deployment order does not change. + *

+ */ + public List propertiesId() { + + ArrayList list = new ArrayList(2); + + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop.isId()) { + list.add(prop); + } + } + + return list; + } + + public DeployBeanPropertyAssocOne findJoinToTable(String tableName) { + + List> assocOne = propertiesAssocOne(); + for (DeployBeanPropertyAssocOne prop : assocOne) { + DeployTableJoin tableJoin = prop.getTableJoin(); + if (tableJoin != null && tableJoin.getTable().equalsIgnoreCase(tableName)) { + return prop; + } + } + return null; + } + + /** + * Return an Iterator of BeanPropertyAssocOne that are not embedded. These are + * effectively joined beans. For ManyToOne and OneToOne associations. + */ + public List> propertiesAssocOne() { + + ArrayList> list = new ArrayList>(); + + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop instanceof DeployBeanPropertyAssocOne) { + if (!prop.isEmbedded()) { + list.add((DeployBeanPropertyAssocOne) prop); + } + } + } + + return list; + + } + + /** + * Return BeanPropertyAssocMany for this descriptor. + */ + public List> propertiesAssocMany() { + + ArrayList> list = new ArrayList>(); + + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop instanceof DeployBeanPropertyAssocMany) { + list.add((DeployBeanPropertyAssocMany) prop); + } + } + + return list; + } + + /** + * Returns 'Version' properties on this bean. These are 'Counter' or 'Update + * Timestamp' type properties. Note version properties can also be on embedded + * beans rather than on the bean itself. + */ + public List propertiesVersion() { + + ArrayList list = new ArrayList(); + + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + + if (prop instanceof DeployBeanPropertyAssoc) { + + } else { + if (!prop.isId() && prop.isVersionColumn()) { + list.add(prop); + } + } + } + + return list; + } + + /** + * base properties without the unique id properties. + */ + public List propertiesBase() { + + ArrayList list = new ArrayList(); + + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + + if (prop instanceof DeployBeanPropertyAssoc) { + + } else { + if (!prop.isId()) { + list.add(prop); + } + } + } + + return list; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanEmbedded.java new file mode 100644 index 000000000..49d0144b9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanEmbedded.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import java.util.HashMap; +import java.util.Map; + +/** + * Collects Deployment information on Embedded beans. + *

+ * Typically collects the overridden column names mapped + * to the Embedded bean. + *

+ */ +public class DeployBeanEmbedded { + + /** + * A map of property names to dbColumns. + */ + Map propMap = new HashMap(); + + /** + * Set a property name to use a specific dbColumn. + */ + public void put(String propertyName, String dbCoumn){ + propMap.put(propertyName, dbCoumn); + } + + /** + * Set a Map of property names to dbColumns. + */ + public void putAll(Map propertyColumnMap){ + propMap.putAll(propertyColumnMap); + } + + /** + * Return a map of property names to dbColumns. + */ + public Map getPropertyColumnMap() { + return propMap; + } + + +} 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 new file mode 100644 index 000000000..103bbd709 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -0,0 +1,965 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.persistence.EmbeddedId; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Version; + +import com.avaje.ebean.annotation.CreatedTimestamp; +import com.avaje.ebean.annotation.UpdatedTimestamp; +import com.avaje.ebean.config.ScalarTypeConverter; +import com.avaje.ebean.config.dbplatform.DbEncrypt; +import com.avaje.ebean.config.dbplatform.DbEncryptFunction; +import com.avaje.ebean.config.ldap.LdapAttributeAdapter; +import com.avaje.ebean.validation.factory.Validator; +import com.avaje.ebeaninternal.server.core.InternString; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.reflect.BeanReflectGetter; +import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.ScalarTypeEnum; +import com.avaje.ebeaninternal.server.type.ScalarTypeWrapper; + +/** + * Description of a property of a bean. Includes its deployment information such + * as database column mapping information. + */ +public class DeployBeanProperty { + + private static final int ID_ORDER = 1000000; + private static final int UNIDIRECTIONAL_ORDER = 100000; + private static final int AUDITCOLUMN_ORDER = -1000000; + private static final int VERSIONCOLUMN_ORDER = -1000000; + + /** + * Advanced bean deployment. To exclude this property from update where + * clause. + */ + public static final String EXCLUDE_FROM_UPDATE_WHERE = "EXCLUDE_FROM_UPDATE_WHERE"; + + /** + * Advanced bean deployment. To exclude this property from delete where + * clause. + */ + public static final String EXCLUDE_FROM_DELETE_WHERE = "EXCLUDE_FROM_DELETE_WHERE"; + + /** + * Advanced bean deployment. To exclude this property from insert. + */ + public static final String EXCLUDE_FROM_INSERT = "EXCLUDE_FROM_INSERT"; + + /** + * Advanced bean deployment. To exclude this property from update set + * clause. + */ + public static final String EXCLUDE_FROM_UPDATE = "EXCLUDE_FROM_UPDATE"; + + /** + * Flag to mark this at part of the unique id. + */ + private boolean id; + + /** + * Flag to mark the property as embedded. This could be on + * BeanPropertyAssocOne rather than here. Put it here for checking Id type + * (embedded or not). + */ + private boolean embedded; + + /** + * Flag indicating if this the version property. + */ + private boolean versionColumn; + + private boolean fetchEager = true; + + /** + * Set if this property is nullable. + */ + private boolean nullable = true; + + private boolean unique; + + private LdapAttributeAdapter ldapAttributeAdapter; + + /** + * The length or precision of the DB column. + */ + private int dbLength; + + private int dbScale; + + private String dbColumnDefn; + + private boolean isTransient; + + private boolean localEncrypted; + + private boolean dbEncrypted; + private DbEncryptFunction dbEncryptFunction; + + private int dbEncryptedType; + + private String dbBind = "?"; + + /** + * Is this property include in database resultSet. + */ + private boolean dbRead; + + /** + * Include this in DB insert. + */ + private boolean dbInsertable; + + /** + * Include this in a DB update. + */ + private boolean dbUpdateable; + + private DeployTableJoin secondaryTableJoin; + + private String secondaryTableJoinPrefix; + + /** + * Set to true if this property is based on a secondary table. + */ + private String secondaryTable; + + /** + * The type that owns this property. + */ + private Class owningType; + + /** + * True if the property is a Clob, Blob LongVarchar or LongVarbinary. + */ + private boolean lob; + + private boolean naturalKey; + + /** + * The logical bean property name. + */ + private String name; + + /** + * The reflected field. + */ + private Field field; + + /** + * The bean type. + */ + private Class propertyType; + + /** + * Set for Non-JDBC types to provide logical to db type conversion. + */ + private ScalarType scalarType; + + /** + * The database column. This can include quoted identifiers. + */ + private String dbColumn; + + private String sqlFormulaSelect; + private String sqlFormulaJoin; + + /** + * The jdbc data type this maps to. + */ + private int dbType; + + /** + * The default value to insert if null. + */ + private Object defaultValue; + + /** + * Extra deployment parameters. + */ + private HashMap extraAttributeMap = new HashMap(); + + /** + * The method used to read the property. + */ + private Method readMethod; + + /** + * The method used to write the property. + */ + private Method writeMethod; + + private BeanReflectGetter getter; + + private BeanReflectSetter setter; + + /** + * Generator for insert or update timestamp etc. + */ + private GeneratedProperty generatedProperty; + + private List validators = new ArrayList(); + + private final DeployBeanDescriptor desc; + + private boolean undirectionalShadow; + + private int sortOrder; + + public DeployBeanProperty(DeployBeanDescriptor desc, Class propertyType, ScalarType scalarType, ScalarTypeConverter typeConverter) { + this.desc = desc; + this.propertyType = propertyType; + this.scalarType = wrapScalarType(propertyType, scalarType, typeConverter); + } + + /** + * Wrap the ScalarType using a ScalarTypeConverter. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private ScalarType wrapScalarType(Class propertyType, ScalarType scalarType, ScalarTypeConverter typeConverter) { + if (typeConverter == null){ + return scalarType; + } + return new ScalarTypeWrapper(propertyType, scalarType, typeConverter); + } + + public int getSortOverride() { + if (field == null) { + return 0; + } + if (field.getAnnotation(Id.class) != null) { + return ID_ORDER; + } else if (field.getAnnotation(EmbeddedId.class) != null) { + return ID_ORDER; + } else if (undirectionalShadow){ + return UNIDIRECTIONAL_ORDER; + } else if (field.getAnnotation(CreatedTimestamp.class) != null) { + return AUDITCOLUMN_ORDER; + } else if (field.getAnnotation(UpdatedTimestamp.class) != null) { + return AUDITCOLUMN_ORDER; + } else if (field.getAnnotation(Version.class) != null) { + return VERSIONCOLUMN_ORDER; + } + return 0; + } + + /** + * Return true is this is a simple scalar property. + */ + public boolean isScalar() { + return true; + } + + public String getFullBeanName() { + return desc.getFullName() + "." + name; + } + + /** + * Return true if this is a primitive type with a nullable DB column. + *

+ * This should log a WARNING as primitive types can't be null. + *

+ */ + public boolean isNullablePrimitive() { + if (nullable && propertyType.isPrimitive()) { + return true; + } + return false; + } + + /** + * Return the DB column length for character columns. + *

+ * Note if there is no length explicitly defined then the scalarType is + * checked to see if that has one (primarily to support putting a length on + * Enum types). + *

+ */ + public int getDbLength() { + if (dbLength == 0 && scalarType != null) { + return scalarType.getLength(); + } + + return dbLength; + } + + /** + * Return the sortOrder for the properties. + */ + public int getSortOrder() { + return sortOrder; + } + + /** + * Set the sortOrder for the properties. + */ + public void setSortOrder(int sortOrder) { + this.sortOrder = sortOrder; + } + + /** + * Return true if this is a placeholder property for a unidirectional relationship. + */ + public boolean isUndirectionalShadow() { + return undirectionalShadow; + } + + /** + * Mark this property as a placeholder for a unidirectional relationship. + */ + public void setUndirectionalShadow(boolean undirectionalShadow) { + this.undirectionalShadow = undirectionalShadow; + } + + /** + * Return true if the property is encrypted in java rather than in the DB. + */ + public boolean isLocalEncrypted() { + return localEncrypted; + } + + /** + * Set to true when the property is encrypted in java rather than in the DB. + */ + public void setLocalEncrypted(boolean localEncrypted) { + this.localEncrypted = localEncrypted; + } + + /** + * Set the DB column length for character columns. + */ + public void setDbLength(int dbLength) { + this.dbLength = dbLength; + } + + /** + * Return the Db scale for numeric columns. + */ + public int getDbScale() { + return dbScale; + } + + /** + * Set the Db scale for numeric columns. + */ + public void setDbScale(int dbScale) { + this.dbScale = dbScale; + } + + /** + * Return the DB column definition if defined. + */ + public String getDbColumnDefn() { + return dbColumnDefn; + } + + /** + * Set a specific DB column definition. + */ + public void setDbColumnDefn(String dbColumnDefn) { + if (dbColumnDefn == null || dbColumnDefn.trim().length() == 0) { + this.dbColumnDefn = null; + } else { + this.dbColumnDefn = InternString.intern(dbColumnDefn); + } + } + + public String getDbConstraintExpression() { + if (scalarType instanceof ScalarTypeEnum) { + // create a check constraint for the enum + ScalarTypeEnum etype = (ScalarTypeEnum) scalarType; + + // check dbColName IN ('A', 'I', 'D') + return "check (" + dbColumn + " in " + etype.getContraintInValues() + ")"; + } + return null; + } + + /** + * Add a validator to this property. + */ + public void addValidator(Validator validator) { + validators.add(validator); + } + + /** + * Return true if the property contains a validator of a given type. + *

+ * Used to detect if a validator has already been assigned when trying to + * automatically add validators such as Length and NotNull. + *

+ */ + public boolean containsValidatorType(Class type) { + + Iterator it = validators.iterator(); + while (it.hasNext()) { + Validator validator = (Validator) it.next(); + if (validator.getClass().equals(type)) { + return true; + } + } + return false; + } + + /** + * Return the validators for this property. + */ + public Validator[] getValidators() { + return validators.toArray(new Validator[validators.size()]); + } + + /** + * Return the scalarType. This returns null for native JDBC types, otherwise + * it is used to convert between logical types and jdbc types. + */ + public ScalarType getScalarType() { + return scalarType; + } + + public void setScalarType(ScalarType scalarType) { + this.scalarType = scalarType; + } + + public BeanReflectGetter getGetter() { + return getter; + } + + public BeanReflectSetter getSetter() { + return setter; + } + + /** + * Return the getter method. + */ + public Method getReadMethod() { + return readMethod; + } + + /** + * Return the setter method. + */ + public Method getWriteMethod() { + return writeMethod; + } + + /** + * Set to the owning type form a Inheritance heirarchy. + */ + public void setOwningType(Class owningType) { + this.owningType = owningType; + } + + public Class getOwningType() { + return owningType; + } + + /** + * Return true if this is local to this type - aka not from a super type. + */ + public boolean isLocal() { + return owningType == null || owningType.equals(desc.getBeanType()); + } + + /** + * Set the getter used to read the property value from a bean. + */ + public void setGetter(BeanReflectGetter getter) { + this.getter = getter; + } + + /** + * Set the setter used to set the property value to a bean. + */ + public void setSetter(BeanReflectSetter setter) { + this.setter = setter; + } + + /** + * Return the name of the property. + */ + public String getName() { + return name; + } + + /** + * Set the name of the property. + */ + public void setName(String name) { + this.name = InternString.intern(name); + } + + /** + * Return the bean Field associated with this property. + */ + public Field getField() { + return field; + } + + /** + * Set the bean Field associated with this property. + */ + public void setField(Field field) { + this.field = field; + } + + public boolean isNaturalKey() { + return naturalKey; + } + + public void setNaturalKey(boolean naturalKey) { + this.naturalKey = naturalKey; + } + + /** + * Return true if this is a generated property like update timestamp and + * create timestamp. + */ + public boolean isGenerated() { + return generatedProperty != null; + } + + /** + * Return the GeneratedValue. Used to generate update timestamp etc. + */ + public GeneratedProperty getGeneratedProperty() { + return generatedProperty; + } + + /** + * Set the GeneratedValue. Used to generate update timestamp etc. + */ + public void setGeneratedProperty(GeneratedProperty generatedValue) { + this.generatedProperty = generatedValue; + } + + /** + * Return true if this property is mandatory. + */ + public boolean isNullable() { + return nullable; + } + + /** + * Set the not nullable of this property. + */ + public void setNullable(boolean isNullable) { + this.nullable = isNullable; + } + + /** + * Return true if the DB column is unique. + */ + public boolean isUnique() { + return unique; + } + + /** + * Set to true if the DB column is unique. + */ + public void setUnique(boolean unique) { + this.unique = unique; + } + + /** + * Return the LdapAttributeAdapter. + */ + public LdapAttributeAdapter getLdapAttributeAdapter() { + return ldapAttributeAdapter; + } + + /** + * Set the LdapAttributeAdapter. + */ + public void setLdapAttributeAdapter(LdapAttributeAdapter ldapAttributeAdapter) { + this.ldapAttributeAdapter = ldapAttributeAdapter; + } + + /** + * Return true if this is a version column used for concurrency checking. + */ + public boolean isVersionColumn() { + return versionColumn; + } + + /** + * Set if this is a version column used for concurrency checking. + */ + public void setVersionColumn(boolean isVersionColumn) { + this.versionColumn = isVersionColumn; + } + + /** + * Return true if this should be eager fetched by default. + */ + public boolean isFetchEager() { + return fetchEager; + } + + /** + * Set the default fetch type for this property. + */ + public void setFetchType(FetchType fetchType) { + this.fetchEager = FetchType.EAGER.equals(fetchType); + } + + /** + * Return the formula this property is based on. + */ + public String getSqlFormulaSelect() { + return sqlFormulaSelect; + } + + public String getSqlFormulaJoin() { + return sqlFormulaJoin; + } + + /** + * The property is based on a formula. + */ + public void setSqlFormula(String formulaSelect, String formulaJoin) { + this.sqlFormulaSelect = formulaSelect; + this.sqlFormulaJoin = formulaJoin.equals("") ? null : formulaJoin; + this.dbRead = true; + this.dbInsertable = false; + this.dbUpdateable = false; + } + + public String getElPlaceHolder(EntityType et) { + if (sqlFormulaSelect != null) { + return sqlFormulaSelect; + } else if (EntityType.LDAP.equals(et)){ + return getDbColumn(); + } else { + if (secondaryTableJoinPrefix != null){ + return "${"+secondaryTableJoinPrefix+"}"+getDbColumn(); + } + // prepend table alias placeholder + return ElPropertyValue.ROOT_ELPREFIX + getDbColumn(); + } + } + + /** + * The database column name this is mapped to. + */ + public String getDbColumn() { + if (sqlFormulaSelect != null) { + return sqlFormulaSelect; + } + return dbColumn; + } + + /** + * Set the database column name this is mapped to. + */ + public void setDbColumn(String dbColumn) { + this.dbColumn = InternString.intern(dbColumn); + } + + /** + * Return the database jdbc data type this is mapped to. + */ + public int getDbType() { + return dbType; + } + + /** + * Set the database jdbc data type this is mapped to. + */ + public void setDbType(int dbType) { + this.dbType = dbType; + this.lob = isLobType(dbType); + } + + /** + * Return true if this is mapped to a Clob Blob LongVarchar or + * LongVarbinary. + */ + public boolean isLob() { + return lob; + } + + private boolean isLobType(int type) { + switch (type) { + case Types.CLOB: + return true; + case Types.BLOB: + return true; + case Types.LONGVARBINARY: + return true; + case Types.LONGVARCHAR: + return true; + + default: + return false; + } + } + + /** + * Return true if this property is based on a secondary table. + */ + public boolean isSecondaryTable() { + return secondaryTable != null; + } + + /** + * Return the secondary table this property is associated with. + */ + public String getSecondaryTable() { + return secondaryTable; + } + + /** + * Set to true if this property is included in persisting. + */ + public void setSecondaryTable(String secondaryTable) { + this.secondaryTable = secondaryTable; + this.dbInsertable = false; + this.dbUpdateable = false; + } + + /** + * + */ + public String getSecondaryTableJoinPrefix() { + return secondaryTableJoinPrefix; + } + + public DeployTableJoin getSecondaryTableJoin() { + return secondaryTableJoin; + } + + public void setSecondaryTableJoin(DeployTableJoin secondaryTableJoin, String prefix) { + this.secondaryTableJoin = secondaryTableJoin; + this.secondaryTableJoinPrefix = prefix; + } + + /** + * Return the DB Bind parameter. Typically is "?" but can be different for + * encrypted bind. + */ + public String getDbBind() { + return dbBind; + } + + /** + * Set the DB bind parameter (if different from "?"). + */ + public void setDbBind(String dbBind) { + this.dbBind = dbBind; + } + + /** + * Return true if this property is encrypted in the DB. + */ + public boolean isDbEncrypted() { + return dbEncrypted; + } + +// /** +// * Set true if this property should be encrypted in the DB. +// */ +// public void setDbEncrypted(boolean dbEncrypted) { +// this.dbEncrypted = dbEncrypted; +// } + + public DbEncryptFunction getDbEncryptFunction() { + return dbEncryptFunction; + } + + public void setDbEncryptFunction(DbEncryptFunction dbEncryptFunction, DbEncrypt dbEncrypt, int dbLen) { + this.dbEncryptFunction = dbEncryptFunction; + this.dbEncrypted = true; + this.dbBind = dbEncryptFunction.getEncryptBindSql(); + + this.dbEncryptedType = isLob() ? Types.BLOB : dbEncrypt.getEncryptDbType(); + if (dbLen > 0){ + setDbLength(dbLen); + } + } + + /** + * Return the DB type for the encrypted property. This can differ from the + * logical type (String encrypted and stored in a VARBINARY) + */ + public int getDbEncryptedType() { + return dbEncryptedType; + } + + /** + * Set the DB type used to store the encrypted value. + */ + public void setDbEncryptedType(int dbEncryptedType) { + this.dbEncryptedType = dbEncryptedType; + } + + /** + * Return true if this property is included in database queries. + */ + public boolean isDbRead() { + return dbRead; + } + + /** + * Set to true if this property is included in database queries. + */ + public void setDbRead(boolean isDBRead) { + this.dbRead = isDBRead; + } + + public boolean isDbInsertable() { + return dbInsertable; + } + + public void setDbInsertable(boolean insertable) { + this.dbInsertable = insertable; + } + + public boolean isDbUpdateable() { + return dbUpdateable; + } + + public void setDbUpdateable(boolean updateable) { + this.dbUpdateable = updateable; + } + + /** + * Return true if the property is transient. + */ + public boolean isTransient() { + return isTransient; + } + + /** + * Mark the property explicitly as a transient property. + */ + public void setTransient(boolean isTransient) { + this.isTransient = isTransient; + } + + /** + * Set the bean read method. + *

+ * NB: That a BeanReflectGetter is used to actually perform the getting of + * property values from a bean. This is due to performance considerations. + *

+ */ + public void setReadMethod(Method readMethod) { + this.readMethod = readMethod; + } + + /** + * Set the bean write method. + *

+ * NB: That a BeanReflectSetter is used to actually perform the setting of + * property values to a bean. This is due to performance considerations. + *

+ */ + public void setWriteMethod(Method writeMethod) { + this.writeMethod = writeMethod; + } + + /** + * Return the property type. + */ + public Class getPropertyType() { + return propertyType; + } + + /** + * Return true if this is included in the unique id. + */ + public boolean isId() { + return id; + } + + /** + * Set to true if this is included in the unique id. + */ + public void setId(boolean id) { + this.id = id; + } + + /** + * Return true if this is an Embedded property. In this case it shares the + * table and pk of its owner object. + */ + public boolean isEmbedded() { + return embedded; + } + + /** + * Set to true if this is an embedded property. + */ + public void setEmbedded(boolean embedded) { + this.embedded = embedded; + } + + public Map getExtraAttributeMap() { + return extraAttributeMap; + } + + /** + * Return an extra attribute set on this property. + */ + public String getExtraAttribute(String key) { + return (String) extraAttributeMap.get(key); + } + + /** + * Set an extra attribute set on this property. + */ + public void setExtraAttribute(String key, String value) { + extraAttributeMap.put(key, value); + } + + /** + * Return the default value. + */ + public Object getDefaultValue() { + return defaultValue; + } + + /** + * Set the default value. Inserted if the value is null. + */ + public void setDefaultValue(Object defaultValue) { + this.defaultValue = defaultValue; + } + + public String toString() { + return desc.getFullName() + "." + name; + } + +} 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 new file mode 100644 index 000000000..841035770 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -0,0 +1,175 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo; +import com.avaje.ebeaninternal.server.deploy.BeanTable; + +/** + * Abstract base for properties mapped to an associated bean, list, set or map. + */ +public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { + + /** + * The type of the joined bean. + */ + Class targetType; + + /** + * Persist settings. + */ + BeanCascadeInfo cascadeInfo = new BeanCascadeInfo(); + + /** + * The join table information. + */ + BeanTable beanTable; + + /** + * Join between the beans. + */ + DeployTableJoin tableJoin = new DeployTableJoin(); + + /** + * Whether the associated join type should be an outer join. + */ + boolean isOuterJoin = false; + + /** + * Literal added to where clause of lazy loading query. + */ + String extraWhere; + + /** + * From the deployment mappedBy attribute. + */ + String mappedBy; + + /** + * Construct the property. + */ + public DeployBeanPropertyAssoc(DeployBeanDescriptor desc, Class targetType) { + super(desc, targetType, null, null); + this.targetType = targetType; + } + + /** + * Return false. + */ + @Override + public boolean isScalar() { + return false; + } + + /** + * Return the type of the target. + *

+ * This is the class of the associated bean, or beans contained in a list, + * set or map. + *

+ */ + public Class getTargetType() { + return targetType; + } + + /** + * Return if this association should use an Outer join. + */ + public boolean isOuterJoin() { + return isOuterJoin; + } + + /** + * Specify that this bean should use an outer join. + */ + public void setOuterJoin(boolean isOuterJoin) { + this.isOuterJoin = isOuterJoin; + } + + /** + * Return a literal expression that is added to the query that lazy loads + * the collection. + */ + public String getExtraWhere() { + return extraWhere; + } + + /** + * Set a literal expression to add to the query that lazy loads the + * collection. + */ + public void setExtraWhere(String extraWhere) { + this.extraWhere = extraWhere; + } + + /** + * return the join to use for the bean. + */ + public DeployTableJoin getTableJoin() { + return tableJoin; + } + + /** + * Return the BeanTable for this association. + *

+ * This has the table name which is used to determine the relationship for + * this association. + *

+ */ + public BeanTable getBeanTable() { + return beanTable; + } + + /** + * Set the bean table. + */ + public void setBeanTable(BeanTable beanTable) { + this.beanTable = beanTable; + getTableJoin().setTable(beanTable.getBaseTable()); + } + + /** + * Get the persist info. + */ + public BeanCascadeInfo getCascadeInfo() { + return cascadeInfo; + } + + + /** + * Return the mappedBy deployment attribute. + *

+ * This is the name of the property in the 'detail' bean that maps back to + * this 'master' bean. + *

+ */ + public String getMappedBy() { + return mappedBy; + } + + /** + * Set mappedBy deployment attribute. + */ + public void setMappedBy(String mappedBy) { + if (!"".equals(mappedBy)) { + this.mappedBy = mappedBy; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java new file mode 100644 index 000000000..693fa1808 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java @@ -0,0 +1,213 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; +import com.avaje.ebeaninternal.server.deploy.ManyType; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Property mapped to a List Set or Map. + */ +public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc { + + ModifyListenMode modifyListenMode = ModifyListenMode.NONE; + + /** + * Flag to indicate manyToMany relationship. + */ + boolean manyToMany; + + /** + * Flag to indicate this is a unidirectional relationship. + */ + boolean unidirectional; + + /** + * Join for manyToMany intersection table. + */ + DeployTableJoin intersectionJoin; + + /** + * For ManyToMany this is the Inverse join used to build reference queries. + */ + DeployTableJoin inverseJoin; + + String fetchOrderBy; + + String mapKey; + + /** + * The type of the many, set, list or map. + */ + ManyType manyType; + + /** + * Create this property. + */ + public DeployBeanPropertyAssocMany(DeployBeanDescriptor desc, Class targetType, ManyType manyType) { + super(desc, targetType); + this.manyType = manyType; + } + + /** + * When generics is not used for manyType you can specify via annotations. + *

+ * Really only expect this for Scala due to a Scala compiler bug at the moment. + * Otherwise I'd probably not bother support this. + *

+ */ + @SuppressWarnings("unchecked") + public void setTargetType(Class cls){ + this.targetType = (Class)cls; + } + + + /** + * Return the many type. + */ + public ManyType getManyType() { + return manyType; + } + + /** + * Return true if this is many to many. + */ + public boolean isManyToMany() { + return manyToMany; + } + + /** + * Set to true if this is a many to many. + */ + public void setManyToMany(boolean isManyToMany) { + this.manyToMany = isManyToMany; + } + + /** + * Return the mode for listening to changes to the List Set or Map. + */ + public ModifyListenMode getModifyListenMode() { + return modifyListenMode; + } + + /** + * Set the mode for listening to changes to the List Set or Map. + */ + public void setModifyListenMode(ModifyListenMode modifyListenMode) { + this.modifyListenMode = modifyListenMode; + } + + /** + * Return true if this is a unidirectional relationship. + */ + public boolean isUnidirectional() { + return unidirectional; + } + + /** + * Set to true if this is a unidirectional relationship. + */ + public void setUnidirectional(boolean unidirectional) { + this.unidirectional = unidirectional; + } + + /** + * Create the immutable version of the intersection join. + */ + public TableJoin createIntersectionTableJoin() { + if (intersectionJoin != null){ + return new TableJoin(intersectionJoin, null); + } else { + return null; + } + } + + /** + * Create the immutable version of the inverse join. + */ + public TableJoin createInverseTableJoin() { + if (inverseJoin != null){ + return new TableJoin(inverseJoin, null); + } else { + return null; + } + } + + /** + * ManyToMany only, join from local table to intersection table. + */ + public DeployTableJoin getIntersectionJoin() { + return intersectionJoin; + } + + public DeployTableJoin getInverseJoin() { + return inverseJoin; + } + + /** + * ManyToMany only, join from local table to intersection table. + */ + public void setIntersectionJoin(DeployTableJoin intersectionJoin) { + this.intersectionJoin = intersectionJoin; + } + + /** + * ManyToMany only, join from foreign table to intersection table. + */ + public void setInverseJoin(DeployTableJoin inverseJoin) { + this.inverseJoin = inverseJoin; + } + + /** + * Return the order by clause used to order the fetching of the data for + * this list, set or map. + */ + public String getFetchOrderBy() { + return fetchOrderBy; + } + + /** + * Return the default mapKey when returning a Map. + */ + public String getMapKey() { + return mapKey; + } + + /** + * Set the default mapKey to use when returning a Map. + */ + public void setMapKey(String mapKey) { + if (mapKey != null && mapKey.length() > 0) { + this.mapKey = mapKey; + } + } + + /** + * Set the order by clause used to order the fetching or the data for this + * list, set or map. + */ + public void setFetchOrderBy(String orderBy) { + if (orderBy != null && orderBy.length() > 0) { + fetchOrderBy = orderBy; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java new file mode 100644 index 000000000..723a152a6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; + + +/** + * Property mapped to a joined bean. + */ +public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { + + boolean oneToOne; + + boolean oneToOneExported; + + boolean importedPrimaryKey; + + DeployBeanEmbedded deployEmbedded; + + /** + * Create the property. + */ + public DeployBeanPropertyAssocOne(DeployBeanDescriptor desc, Class targetType) { + super(desc, targetType); + } + + /** + * Return the deploy information specifically for the deployment + * of Embedded beans. + */ + public DeployBeanEmbedded getDeployEmbedded() { + // deployment should be single threaded + if (deployEmbedded == null){ + deployEmbedded = new DeployBeanEmbedded(); + } + return deployEmbedded; + } + + @Override + public String getDbColumn() { + DeployTableJoinColumn[] columns = tableJoin.columns(); + if (columns.length == 1){ + return columns[0].getLocalDbColumn(); + } + return super.getDbColumn(); + } + + @Override + public String getElPlaceHolder(EntityType et) { + return super.getElPlaceHolder(et); + } + + /** + * Return true if this a OneToOne property. Otherwise assumed ManyToOne. + */ + public boolean isOneToOne() { + return oneToOne; + } + + /** + * Set to true if this is a OneToOne. + */ + public void setOneToOne(boolean oneToOne) { + this.oneToOne = oneToOne; + } + + /** + * Return true if this is the exported side of a OneToOne. + */ + public boolean isOneToOneExported() { + return oneToOneExported; + } + + /** + * Set to true if this is the exported side of a OneToOne. This means + * it doesn't 'own' the foreign key column. A OneToMany without the many. + */ + public void setOneToOneExported(boolean oneToOneExported) { + this.oneToOneExported = oneToOneExported; + } + + /** + * If true this bean maps to the primary key. + */ + public boolean isImportedPrimaryKey() { + return importedPrimaryKey; + } + + /** + * Set to true if the bean maps to the primary key. + */ + public void setImportedPrimaryKey(boolean importedPrimaryKey) { + this.importedPrimaryKey = importedPrimaryKey; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyCompound.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyCompound.java new file mode 100644 index 000000000..1947d0331 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyCompound.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import com.avaje.ebean.config.ScalarTypeConverter; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorMap; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompoundRoot; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompoundScalar; +import com.avaje.ebeaninternal.server.type.CtCompoundProperty; +import com.avaje.ebeaninternal.server.type.CtCompoundType; +import com.avaje.ebeaninternal.server.type.CtCompoundTypeScalarList; +import com.avaje.ebeaninternal.server.type.ScalarType; + + +/** + * Property mapped to a joined bean. + */ +public class DeployBeanPropertyCompound extends DeployBeanProperty { + + final CtCompoundType compoundType; + + final ScalarTypeConverter typeConverter; + + DeployBeanEmbedded deployEmbedded; + + /** + * Create the property. + */ + public DeployBeanPropertyCompound(DeployBeanDescriptor desc, Class targetType, + CtCompoundType compoundType, ScalarTypeConverter typeConverter) { + + super(desc, targetType, null, null); + this.compoundType = compoundType; + this.typeConverter = typeConverter; + } + + public BeanPropertyCompoundRoot getFlatProperties(BeanDescriptorMap owner, BeanDescriptor descriptor) { + + // get a 'flat' list of all the scalar types, their relative property names + // and also set their matching dbColumn + + // represents the root property + BeanPropertyCompoundRoot rootProperty = new BeanPropertyCompoundRoot(this); + + // Walk the tree of a compound type collecting the + // scalar types and non-scalar properties + CtCompoundTypeScalarList ctMeta = new CtCompoundTypeScalarList(); + + compoundType.accumulateScalarTypes(null, ctMeta); + + List beanPropertyList = new ArrayList(); + + + // for each of the scalar types inside a compound value object + // build a BeanPropertyCompoundScalar with appropriate deployment + // information. + + for (Entry> entry : ctMeta.entries()) { + + String relativePropertyName = entry.getKey(); + ScalarType scalarType = entry.getValue(); + + CtCompoundProperty ctProp = ctMeta.getCompoundType(relativePropertyName); + + + String dbColumn = relativePropertyName.replace(".", "_"); + dbColumn = getDbColumn(relativePropertyName, dbColumn); + + DeployBeanProperty deploy = new DeployBeanProperty(null, scalarType.getType(), scalarType, null); + deploy.setScalarType(scalarType); + deploy.setDbColumn(dbColumn); + deploy.setName(relativePropertyName); + deploy.setDbInsertable(true); + deploy.setDbUpdateable(true); + deploy.setDbRead(true); + + BeanPropertyCompoundScalar bp = new BeanPropertyCompoundScalar(rootProperty, deploy, ctProp, typeConverter); + beanPropertyList.add(bp); + + rootProperty.register(bp); + } + + rootProperty.setNonScalarProperties(ctMeta.getNonScalarProperties()); + return rootProperty; + } + + private String getDbColumn(String propName, String defaultDbColumn){ + if (deployEmbedded == null){ + return defaultDbColumn; + } + String dbColumn = deployEmbedded.getPropertyColumnMap().get(propName); + return dbColumn == null ? defaultDbColumn : dbColumn; + } + + /** + * Return the deploy information specifically for the deployment + * of Embedded beans. + */ + public DeployBeanEmbedded getDeployEmbedded() { + // deployment should be single threaded + if (deployEmbedded == null){ + deployEmbedded = new DeployBeanEmbedded(); + } + return deployEmbedded; + } + + public ScalarTypeConverter getTypeConverter() { + return typeConverter; + } + + public CtCompoundType getCompoundType() { + return compoundType; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java new file mode 100644 index 000000000..ff62d6ec7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java @@ -0,0 +1,403 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; + +import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; +import com.avaje.ebean.validation.factory.Validator; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorMap; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.deploy.BeanPropertySimpleCollection; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Helper object to classify BeanProperties into appropriate lists. + */ +public class DeployBeanPropertyLists { + + private BeanProperty derivedFirstVersionProp; + + private final BeanDescriptor desc; + + private final LinkedHashMap propertyMap; + + private final ArrayList ids = new ArrayList(); + + private final ArrayList version = new ArrayList(); + + private final ArrayList local = new ArrayList(); + + private final ArrayList manys = new ArrayList(); + private final ArrayList nonManys = new ArrayList(); + + private final ArrayList ones = new ArrayList(); + + private final ArrayList onesExported = new ArrayList(); + + private final ArrayList onesImported = new ArrayList(); + + private final ArrayList embedded = new ArrayList(); + + private final ArrayList baseScalar = new ArrayList(); + + private final ArrayList baseCompound = new ArrayList(); + + private final ArrayList transients = new ArrayList(); + + private final ArrayList nonTransients = new ArrayList(); + + private final TableJoin[] tableJoins; + + private final BeanPropertyAssocOne unidirectional; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public DeployBeanPropertyLists(BeanDescriptorMap owner, BeanDescriptor desc, DeployBeanDescriptor deploy) { + this.desc = desc; + + DeployBeanPropertyAssocOne deployUnidirectional = deploy.getUnidirectional(); + if (deployUnidirectional == null) { + unidirectional = null; + } else { + unidirectional = new BeanPropertyAssocOne(owner, desc, deployUnidirectional); + } + + this.propertyMap = new LinkedHashMap(); + + Iterator deployIt = deploy.propertiesAll(); + while (deployIt.hasNext()) { + DeployBeanProperty deployProp = deployIt.next(); + BeanProperty beanProp = createBeanProperty(owner, deployProp); + propertyMap.put(beanProp.getName(), beanProp); + } + + Iterator it = propertyMap.values().iterator(); + + int order = 0; + while (it.hasNext()) { + BeanProperty prop = it.next(); + prop.setDeployOrder(order++); + allocateToList(prop); + } + + List deployTableJoins = deploy.getTableJoins(); + tableJoins = new TableJoin[deployTableJoins.size()]; + for (int i = 0; i < deployTableJoins.size(); i++) { + tableJoins[i] = new TableJoin(deployTableJoins.get(i), propertyMap); + } + + } + + /** + * Return the unidirectional. + */ + public BeanPropertyAssocOne getUnidirectional() { + return unidirectional; + } + + /** + * Allocate the property to a list. + */ + private void allocateToList(BeanProperty prop) { + if (prop.isTransient()) { + transients.add(prop); + return; + } + if (prop.isId()) { + ids.add(prop); + return; + } else { + nonTransients.add(prop); + } + + if (desc.getInheritInfo() != null && prop.isLocal()) { + local.add(prop); + } + + if (prop instanceof BeanPropertyAssocMany) { + manys.add(prop); + + } else { + nonManys.add(prop); + if (prop instanceof BeanPropertyAssocOne) { + if (prop.isEmbedded()) { + embedded.add(prop); + } else { + ones.add(prop); + BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne) prop; + if (assocOne.isOneToOneExported()) { + onesExported.add(prop); + } else { + onesImported.add(prop); + } + } + } else { + // its a "base" property... + if (prop.isVersion()) { + version.add(prop); + if (derivedFirstVersionProp == null) { + derivedFirstVersionProp = prop; + } + } + if (prop instanceof BeanPropertyCompound) { + baseCompound.add((BeanPropertyCompound) prop); + } else { + baseScalar.add(prop); + } + } + } + } + + public BeanProperty getFirstVersion() { + return derivedFirstVersionProp; + } + + public BeanProperty[] getPropertiesWithValidators(boolean recurse) { + + ArrayList list = new ArrayList(); + Iterator it = propertyMap.values().iterator(); + while (it.hasNext()) { + BeanProperty property = (BeanProperty) it.next(); + if (property.hasValidationRules(recurse)) { + list.add(property); + } + } + return list.toArray(new BeanProperty[list.size()]); + } + + public Validator[] getBeanValidators() { + return new Validator[0]; + } + + public LinkedHashMap getPropertyMap() { + return propertyMap; + } + + public TableJoin[] getTableJoin() { + return tableJoins; + } + + /** + * Return the base scalar properties (excludes Id and secondary table + * properties). + */ + public BeanProperty[] getBaseScalar() { + return (BeanProperty[]) baseScalar.toArray(new BeanProperty[baseScalar.size()]); + } + + public BeanPropertyCompound[] getBaseCompound() { + return (BeanPropertyCompound[]) baseCompound.toArray(new BeanPropertyCompound[baseCompound.size()]); + } + + public BeanProperty getNaturalKey() { + String naturalKey = desc.getCacheOptions().getNaturalKey(); + if (naturalKey != null){ + return propertyMap.get(naturalKey); + } + return null; + } + + public BeanProperty[] getId() { + return (BeanProperty[]) ids.toArray(new BeanProperty[ids.size()]); + } + + public BeanProperty[] getNonTransients() { + return (BeanProperty[]) nonTransients.toArray(new BeanProperty[nonTransients.size()]); + } + + public BeanProperty[] getTransients() { + return (BeanProperty[]) transients.toArray(new BeanProperty[transients.size()]); + } + + public BeanProperty[] getVersion() { + return (BeanProperty[]) version.toArray(new BeanProperty[version.size()]); + } + + public BeanProperty[] getLocal() { + return (BeanProperty[]) local.toArray(new BeanProperty[local.size()]); + } + + public BeanPropertyAssocOne[] getEmbedded() { + return (BeanPropertyAssocOne[]) embedded.toArray(new BeanPropertyAssocOne[embedded.size()]); + } + + public BeanPropertyAssocOne[] getOneExported() { + return (BeanPropertyAssocOne[]) onesExported.toArray(new BeanPropertyAssocOne[onesExported.size()]); + } + + public BeanPropertyAssocOne[] getOneImported() { + return (BeanPropertyAssocOne[]) onesImported.toArray(new BeanPropertyAssocOne[onesImported.size()]); + } + + public BeanPropertyAssocOne[] getOnes() { + return (BeanPropertyAssocOne[]) ones.toArray(new BeanPropertyAssocOne[ones.size()]); + } + + public BeanPropertyAssocOne[] getOneExportedSave() { + return getOne(false, Mode.Save); + } + + public BeanPropertyAssocOne[] getOneExportedDelete() { + return getOne(false, Mode.Delete); + } + + public BeanPropertyAssocOne[] getOneImportedSave() { + return getOne(true, Mode.Save); + } + + public BeanPropertyAssocOne[] getOneImportedDelete() { + return getOne(true, Mode.Delete); + } + + public BeanProperty[] getNonMany() { + return (BeanProperty[]) nonManys.toArray(new BeanProperty[nonManys.size()]); + } + + public BeanPropertyAssocMany[] getMany() { + return (BeanPropertyAssocMany[]) manys.toArray(new BeanPropertyAssocMany[manys.size()]); + } + + public BeanPropertyAssocMany[] getManySave() { + return getMany(Mode.Save); + } + + public BeanPropertyAssocMany[] getManyDelete() { + return getMany(Mode.Delete); + } + + public BeanPropertyAssocMany[] getManyToMany() { + return getMany2Many(); + } + + /** + * Mode used to determine which BeanPropertyAssoc to include. + */ + private enum Mode { + Save, Delete, Validate; + } + + private BeanPropertyAssocOne[] getOne(boolean imported, Mode mode) { + ArrayList> list = new ArrayList>(); + for (int i = 0; i < ones.size(); i++) { + BeanPropertyAssocOne prop = (BeanPropertyAssocOne) ones.get(i); + if (imported != prop.isOneToOneExported()) { + switch (mode) { + case Save: + if (prop.getCascadeInfo().isSave()) { + list.add(prop); + } + break; + case Delete: + if (prop.getCascadeInfo().isDelete()) { + list.add(prop); + } + break; + case Validate: + if (prop.getCascadeInfo().isValidate()) { + list.add(prop); + } + break; + default: + break; + } + } + } + + return (BeanPropertyAssocOne[]) list.toArray(new BeanPropertyAssocOne[list.size()]); + } + + private BeanPropertyAssocMany[] getMany2Many() { + ArrayList> list = new ArrayList>(); + for (int i = 0; i < manys.size(); i++) { + BeanPropertyAssocMany prop = (BeanPropertyAssocMany) manys.get(i); + if (prop.isManyToMany()) { + list.add(prop); + } + } + + return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[list.size()]); + } + + private BeanPropertyAssocMany[] getMany(Mode mode) { + ArrayList> list = new ArrayList>(); + for (int i = 0; i < manys.size(); i++) { + BeanPropertyAssocMany prop = (BeanPropertyAssocMany) manys.get(i); + + switch (mode) { + case Save: + if (prop.getCascadeInfo().isSave() || prop.isManyToMany() + || ModifyListenMode.REMOVALS.equals(prop.getModifyListenMode())) { + // Note ManyToMany always included as we always 'save' + // the relationship via insert/delete of intersection table + // REMOVALS means including PrivateOwned relationships + list.add(prop); + } + break; + case Delete: + if (prop.getCascadeInfo().isDelete() + || ModifyListenMode.REMOVALS.equals(prop.getModifyListenMode())) { + // REMOVALS means including PrivateOwned relationships + list.add(prop); + } + break; + case Validate: + if (prop.getCascadeInfo().isValidate()) { + list.add(prop); + } + break; + default: + break; + } + + } + + return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[list.size()]); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private BeanProperty createBeanProperty(BeanDescriptorMap owner, DeployBeanProperty deployProp) { + + if (deployProp instanceof DeployBeanPropertyAssocOne) { + + return new BeanPropertyAssocOne(owner, desc, (DeployBeanPropertyAssocOne) deployProp); + } + if (deployProp instanceof DeployBeanPropertySimpleCollection) { + + return new BeanPropertySimpleCollection(owner, desc, (DeployBeanPropertySimpleCollection)deployProp); + } + if (deployProp instanceof DeployBeanPropertyAssocMany) { + + return new BeanPropertyAssocMany(owner, desc, (DeployBeanPropertyAssocMany) deployProp); + } + if (deployProp instanceof DeployBeanPropertyCompound) { + + return new BeanPropertyCompound(owner, desc, (DeployBeanPropertyCompound) deployProp); + } + + return new BeanProperty(owner, desc, deployProp); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertySimpleCollection.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertySimpleCollection.java new file mode 100644 index 000000000..1739217ba --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertySimpleCollection.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; +import com.avaje.ebeaninternal.server.deploy.ManyType; +import com.avaje.ebeaninternal.server.type.ScalarType; + +public class DeployBeanPropertySimpleCollection extends DeployBeanPropertyAssocMany { + + private final ScalarType collectionScalarType; + + public DeployBeanPropertySimpleCollection(DeployBeanDescriptor desc, Class targetType, ScalarType scalarType, ManyType manyType) { + super(desc, targetType, manyType); + this.collectionScalarType = scalarType; + this.modifyListenMode = ModifyListenMode.ALL; + } + + /** + * Return the scalarType of the collection elements. + */ + public ScalarType getCollectionScalarType() { + return collectionScalarType; + } + + /** + * Returns false as never a ManyToMany. + */ + @Override + public boolean isManyToMany() { + return false; + } + + /** + * Returns true as always Unidirectional. + */ + @Override + public boolean isUnidirectional() { + return true; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanTable.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanTable.java new file mode 100644 index 000000000..1876c4094 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanTable.java @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorMap; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; + + +/** + * Used for associated beans in place of a BeanDescriptor. This is done to avoid + * recursion issues due to the potentially bi-directional and circular + * relationships between beans. + *

+ * It holds the main deployment information and not all the detail that is held + * in a BeanDescriptor. + *

+ */ +public class DeployBeanTable { + + private final Class beanType; + + /** + * The base table. + */ + private String baseTable; + + private List idProperties; + + /** + * Create the BeanTable. + */ + public DeployBeanTable(Class beanType) { + this.beanType = beanType; + } + + /** + * Return the base table for this BeanTable. + * This is used to determine the join information + * for associations. + */ + public String getBaseTable() { + return baseTable; + } + + /** + * Set the base table for this BeanTable. + */ + public void setBaseTable(String baseTable) { + this.baseTable = baseTable; + } + + /** + * Return the id properties. + */ + public BeanProperty[] createIdProperties(BeanDescriptorMap owner) { + BeanProperty[] props = new BeanProperty[idProperties.size()]; + for (int i = 0; i < idProperties.size(); i++) { + props[i] = createProperty(owner, idProperties.get(i)); + } + return props; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private BeanProperty createProperty(BeanDescriptorMap owner, DeployBeanProperty prop){ + + if (prop instanceof DeployBeanPropertyAssocOne){ + return new BeanPropertyAssocOne(owner, (DeployBeanPropertyAssocOne)prop); + + } else { + return new BeanProperty(prop); + } + + } + + /** + * Set the Id properties. + */ + public void setIdProperties(List idProperties) { + this.idProperties = idProperties; + } + + /** + * Return the class for this beanTable. + */ + public Class getBeanType() { + return beanType; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java new file mode 100644 index 000000000..900039701 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java @@ -0,0 +1,227 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import java.util.ArrayList; + +import javax.persistence.JoinColumn; + +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo; +import com.avaje.ebeaninternal.server.deploy.BeanTable; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Represents a join to another table during deployment phase. + *

+ * This gets converted into a immutable TableJoin when complete. + *

+ */ +public class DeployTableJoin { + + /** + * Flag set when the imported key maps to the primary key. + * This occurs for intersection tables (ManyToMany). + */ + private boolean importedPrimaryKey; + + /** + * The joined table. + */ + private String table; + + /** + * The type of join. LEFT OUTER etc. + */ + private String type = TableJoin.JOIN; + + /** + * The list of properties mapped to this joined table. + */ + private ArrayList properties = new ArrayList(); + + /** + * The list of join column pairs. Used to generate the on clause. + */ + private ArrayList columns = new ArrayList(); + + /** + * The persist cascade info. + */ + private BeanCascadeInfo cascadeInfo = new BeanCascadeInfo(); + + + /** + * Create a DeployTableJoin. + */ + public DeployTableJoin() { + } + + public String toString() { + return type + " " + table + " " + columns; + } + + /** + * Return true if the imported foreign key maps to the primary key. + */ + public boolean isImportedPrimaryKey() { + return importedPrimaryKey; + } + + /** + * Flag set when the imported key maps to the primary key. + * This occurs for intersection tables (ManyToMany). + */ + public void setImportedPrimaryKey(boolean importedPrimaryKey) { + this.importedPrimaryKey = importedPrimaryKey; + } + + /** + * Return true if the JoinOnPair have been set. + */ + public boolean hasJoinColumns() { + return columns.size() > 0; + } + + /** + * Return the persist info. + */ + public BeanCascadeInfo getCascadeInfo() { + return cascadeInfo; + } + + + /** + * Copy all the columns to this join potentially reversing the columns. + */ + public void setColumns(DeployTableJoinColumn[] cols, boolean reverse) { + columns = new ArrayList(); + for (int i = 0; i < cols.length; i++) { + addJoinColumn(cols[i].copy(reverse)); + } + } + + /** + * Add a join pair + */ + public void addJoinColumn(DeployTableJoinColumn pair) { + columns.add(pair); + } + + /** + * Add a JoinColumn + *

+ * The order is generally true for OneToMany and false for ManyToOne relationships. + *

+ */ + public void addJoinColumn(boolean order, JoinColumn jc, BeanTable beanTable) { + if (!"".equals(jc.table())) { + setTable(jc.table()); + } + addJoinColumn(new DeployTableJoinColumn(order, jc, beanTable)); + } + + /** + * Add a JoinColumn array. + */ + public void addJoinColumn(boolean order, JoinColumn[] jcArray, BeanTable beanTable) { + for (int i = 0; i < jcArray.length; i++) { + addJoinColumn(order, jcArray[i], beanTable); + } + } + + /** + * Return the join columns. + */ + public DeployTableJoinColumn[] columns() { + return (DeployTableJoinColumn[])columns.toArray(new DeployTableJoinColumn[columns.size()]); + } + + + /** + * For secondary table joins returns the properties mapped to that table. + */ + public DeployBeanProperty[] properties() { + return (DeployBeanProperty[])properties.toArray(new DeployBeanProperty[properties.size()]); + } + + /** + * Return the joined table name. + */ + public String getTable() { + return table; + } + + /** + * set the joined table name. + */ + public void setTable(String table) { + this.table = table; + } + + /** + * Return the type of join. LEFT OUTER JOIN etc. + */ + public String getType() { + return type; + } + + /** + * Return true if this join is a left outer join. + */ + public boolean isOuterJoin() { + return type.equals(TableJoin.LEFT_OUTER); + } + + /** + * Set the type of join. + */ + public void setType(String joinType) { + joinType = joinType.toUpperCase(); + if (joinType.equalsIgnoreCase(TableJoin.JOIN)) { + type = TableJoin.JOIN; + } else if (joinType.indexOf("LEFT") > -1) { + type = TableJoin.LEFT_OUTER; + } else if (joinType.indexOf("OUTER") > -1) { + type = TableJoin.LEFT_OUTER; + } else if (joinType.indexOf("INNER") > -1) { + type = TableJoin.JOIN; + } else { + throw new RuntimeException(Message.msg("join.type.unknown", joinType)); + } + } + + public DeployTableJoin createInverse(String tableName) { + + DeployTableJoin inverse = new DeployTableJoin(); + + return copyTo(inverse, true, tableName); + + } + + public DeployTableJoin copyTo(DeployTableJoin destJoin, boolean reverse, String tableName) { + + destJoin.setTable(tableName); + destJoin.setType(type); + destJoin.setColumns(columns(), reverse); + + return destJoin; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java new file mode 100644 index 000000000..ac8811781 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java @@ -0,0 +1,197 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.meta; + +import javax.persistence.JoinColumn; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanTable; + +/** + * A join pair of local and foreign properties. + */ +public class DeployTableJoinColumn { + + /** + * The local database column name. + */ + String localDbColumn; + + /** + * The foreign database column name. + */ + String foreignDbColumn; + + boolean insertable; + + boolean updateable; + + /** + * Construct when automatically determining the join. + *

+ * Assume that we want the foreign key to be insertable and updateable. + *

+ */ + public DeployTableJoinColumn(String localDbColumn, String foreignDbColumn) { + this(localDbColumn, foreignDbColumn, true, true); + } + + /** + * Construct with explicit insertable and updateable flags. + */ + public DeployTableJoinColumn(String localDbColumn, String foreignDbColumn, boolean insertable, boolean updateable) { + this.localDbColumn = nullEmptyString(localDbColumn); + this.foreignDbColumn = nullEmptyString(foreignDbColumn); + this.insertable = insertable; + this.updateable = updateable; + } + + public DeployTableJoinColumn(boolean order, JoinColumn jc, BeanTable beanTable) { + this(jc.referencedColumnName(), jc.name(), jc.insertable(), jc.updatable()); + setReferencedColumn(beanTable); + if (!order){ + reverse(); + } + } + + private void setReferencedColumn(BeanTable beanTable){ + if (localDbColumn == null){ + BeanProperty[] idProperties = beanTable.getIdProperties(); + if (idProperties.length == 1){ + localDbColumn = idProperties[0].getDbColumn(); + } + } + } + + /** + * Reverse the direction of the join. + */ + public DeployTableJoinColumn reverse() { + String temp = localDbColumn; + localDbColumn = foreignDbColumn; + foreignDbColumn = temp; + return this; + } + + /** + * Helper method to null out empty strings. + */ + private String nullEmptyString(String s){ + if ("".equals(s)){ + return null; + } + return s; + } + + + public DeployTableJoinColumn copy(boolean reverse) { + // Note that the insertable and updateable are just copied + // which may not always be the correct thing to do + // but will leave it like this for now + if (reverse){ + return new DeployTableJoinColumn(foreignDbColumn, localDbColumn, insertable, updateable); + + } else { + return new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable); + } + } + + public String toString() { + return localDbColumn + " = " + foreignDbColumn; + } + + /** + * Return true if either the local or foreign column is null. + *

+ * Both columns need to be defined. If one is null then typically it is + * derived as the primary key column. + *

+ */ + public boolean hasNullColumn() { + return localDbColumn == null || foreignDbColumn == null; + } + + /** + * When only ONE column has been set by deployment information return that one. + *

+ * Used with hasNullColumn() to set the foreignDbColumn for OneToMany joins. + *

+ */ + public String getNonNullColumn() { + if (localDbColumn == null && foreignDbColumn == null) { + throw new IllegalStateException("expecting only one null column?"); + + } else if (localDbColumn != null && foreignDbColumn != null) { + throw new IllegalStateException("expecting one null column?"); + } + if (localDbColumn != null) { + return localDbColumn; + } else { + return foreignDbColumn; + } + } + + /** + * Return true if this column should be insertable. + */ + public boolean isInsertable() { + return insertable; + } + + /** + * Return true if this column should be updateable. + */ + public boolean isUpdateable() { + return updateable; + } + + /** + * Return the foreign database column name. + */ + public String getForeignDbColumn() { + return foreignDbColumn; + } + + /** + * Set the foreign database column name. + *

+ * Used when this is derived from Primary Key and not set explicitly in the + * deployment information. + *

+ */ + public void setForeignDbColumn(String foreignDbColumn) { + this.foreignDbColumn = foreignDbColumn; + } + + /** + * Return the local database column name. + */ + public String getLocalDbColumn() { + return localDbColumn; + } + + /** + * Set the local database column name. + */ + public void setLocalDbColumn(String localDbColumn) { + this.localDbColumn = localDbColumn; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/package.html b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/package.html new file mode 100644 index 000000000..847f20537 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/package.html @@ -0,0 +1,10 @@ + + + + Temporary objects used to collect deployment information + + +Temporary objects used to collect deployment information + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/package.html b/src/main/java/com/avaje/ebeaninternal/server/deploy/package.html new file mode 100644 index 000000000..4905b8192 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/package.html @@ -0,0 +1,14 @@ + + + + Bean description + + +Bean description + +

+Describes beans with their properties and configuration. +

+ + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java new file mode 100644 index 000000000..3a4bc2126 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -0,0 +1,348 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.util.Iterator; + +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +import com.avaje.ebean.annotation.LdapAttribute; +import com.avaje.ebean.annotation.PrivateOwned; +import com.avaje.ebean.annotation.Where; +import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebean.config.TableName; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanTable; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +/** + * Read the deployment annotation for Assoc Many beans. + */ +public class AnnotationAssocManys extends AnnotationParser { + + private final BeanDescriptorManager factory; + + /** + * Create with the DeployInfo. + */ + public AnnotationAssocManys(DeployBeanInfo info, BeanDescriptorManager factory) { + super(info); + this.factory = factory; + } + + /** + * Parse the annotations. + */ + public void parse() { + Iterator it = descriptor.propertiesAll(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop instanceof DeployBeanPropertyAssocMany) { + read((DeployBeanPropertyAssocMany) prop); + } + } + } + + private void read(DeployBeanPropertyAssocMany prop) { + + OneToMany oneToMany = get(prop, OneToMany.class); + if (oneToMany != null) { + readToOne(oneToMany, prop); + PrivateOwned privateOwned = get(prop, PrivateOwned.class); + if (privateOwned != null){ + prop.setModifyListenMode(ModifyListenMode.REMOVALS); + prop.getCascadeInfo().setDelete(privateOwned.cascadeRemove()); + } + } + ManyToMany manyToMany = get(prop, ManyToMany.class); + if (manyToMany != null) { + readToMany(manyToMany, prop); + } + + OrderBy orderBy = get(prop, OrderBy.class); + if (orderBy != null) { + prop.setFetchOrderBy(orderBy.value()); + } + + MapKey mapKey = get(prop, MapKey.class); + if (mapKey != null) { + prop.setMapKey(mapKey.name()); + } + + Where where = get(prop, Where.class); + if (where != null) { + prop.setExtraWhere(where.clause()); + } + + // check for manually defined joins + BeanTable beanTable = prop.getBeanTable(); + JoinColumn joinColumn = get(prop, JoinColumn.class); + if (joinColumn != null) { + prop.getTableJoin().addJoinColumn(true, joinColumn, beanTable); + } + + JoinColumns joinColumns = get(prop, JoinColumns.class); + if (joinColumns != null) { + prop.getTableJoin().addJoinColumn(true, joinColumns.value(), beanTable); + } + + JoinTable joinTable = get(prop, JoinTable.class); + if (joinTable != null) { + if (prop.isManyToMany()){ + // expected this + readJoinTable(joinTable, prop); + + } else { + // OneToMany in theory + prop.getTableJoin().addJoinColumn(true, joinTable.joinColumns(), beanTable); + } + } + LdapAttribute ldapAttribute = get(prop, LdapAttribute.class); + if (ldapAttribute != null) { + // read ldap specific property settings + readLdapAttribute(ldapAttribute, prop); + } + + if (prop.getMappedBy() != null){ + // the join is derived by reversing the join information + // from the mapped by property. + // Refer BeanDescriptorManager.readEntityRelationships() + return; + } + + if (prop.isManyToMany()){ + manyToManyDefaultJoins(prop); + return; + } + + + if (!prop.getTableJoin().hasJoinColumns() && beanTable != null){ + + // use naming convention to define join (based on the bean name for this side of relationship) + // A unidirectional OneToMany or OneToMany with no mappedBy property + + NamingConvention nc = factory.getNamingConvention(); + + String fkeyPrefix = null; + if (nc.isUseForeignKeyPrefix()){ + fkeyPrefix = nc.getColumnFromProperty(descriptor.getBeanType(), descriptor.getName()); + } + + // Use the owning bean table to define the join + BeanTable owningBeanTable = factory.getBeanTable(descriptor.getBeanType()); + owningBeanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), false); + } + } + + /** + * Define the joins for a ManyToMany relationship. + *

+ * This includes joins to the intersection table and from the intersection table + * to the other side of the ManyToMany. + *

+ */ + private void readJoinTable(JoinTable joinTable, DeployBeanPropertyAssocMany prop) { + + String intTableName = getFullTableName(joinTable); + // set the intersection table + DeployTableJoin intJoin = new DeployTableJoin(); + intJoin.setTable(intTableName); + + // add the source to intersection join columns + intJoin.addJoinColumn(true, joinTable.joinColumns(), prop.getBeanTable()); + + // set the intersection to dest table join columns + DeployTableJoin destJoin = prop.getTableJoin(); + destJoin.addJoinColumn(false, joinTable.inverseJoinColumns(), prop.getBeanTable()); + + intJoin.setType(TableJoin.LEFT_OUTER); + + // reverse join from dest back to intersection + DeployTableJoin inverseDest = destJoin.createInverse(intTableName); + prop.setIntersectionJoin(intJoin); + prop.setInverseJoin(inverseDest); + } + + /** + * Return the full table name + * @param joinTable + * @return + */ + private String getFullTableName(JoinTable joinTable) { + StringBuilder sb = new StringBuilder(); + if (!StringHelper.isNull(joinTable.catalog())){ + sb.append(joinTable.catalog()).append("."); + } + if (!StringHelper.isNull(joinTable.schema())){ + sb.append(joinTable.schema()).append("."); + } + sb.append(joinTable.name()); + return sb.toString(); + } + + /** + * Define intersection table and foreign key columns for ManyToMany. + *

+ * Some of these (maybe all) have been already defined via @JoinTable + * and @JoinColumns etc. + *

+ */ + private void manyToManyDefaultJoins(DeployBeanPropertyAssocMany prop) { + + String intTableName = null; + + DeployTableJoin intJoin = prop.getIntersectionJoin(); + if (intJoin == null){ + intJoin = new DeployTableJoin(); + prop.setIntersectionJoin(intJoin); + } else { + // intersection table already defined (by @JoinTable) + intTableName = intJoin.getTable(); + } + + BeanTable localTable = factory.getBeanTable(descriptor.getBeanType()); + BeanTable otherTable = factory.getBeanTable(prop.getTargetType()); + + final String localTableName = localTable.getUnqualifiedBaseTable(); + final String otherTableName = otherTable.getUnqualifiedBaseTable(); + + if (intTableName == null){ + // define intersection table name + intTableName = getM2MJoinTableName(localTable, otherTable); + + intJoin.setTable(intTableName); + intJoin.setType(TableJoin.LEFT_OUTER); + } + + DeployTableJoin destJoin = prop.getTableJoin(); + + + if (intJoin.hasJoinColumns() && destJoin.hasJoinColumns()){ + // already defined the foreign key columns etc + return; + } + if (!intJoin.hasJoinColumns()){ + // define foreign key columns + BeanProperty[] localIds = localTable.getIdProperties(); + for (int i = 0; i < localIds.length; i++) { + // add the source to intersection join columns + String fkCol = localTableName+"_"+localIds[i].getDbColumn(); + intJoin.addJoinColumn(new DeployTableJoinColumn(localIds[i].getDbColumn(), fkCol)); + } + } + + if (!destJoin.hasJoinColumns()){ + // define inverse foreign key columns + BeanProperty[] otherIds = otherTable.getIdProperties(); + for (int i = 0; i < otherIds.length; i++) { + // set the intersection to dest table join columns + final String fkCol = otherTableName+"_"+otherIds[i].getDbColumn(); + destJoin.addJoinColumn(new DeployTableJoinColumn(fkCol, otherIds[i].getDbColumn())); + } + } + + // reverse join from dest back to intersection + DeployTableJoin inverseDest = destJoin.createInverse(intTableName); + prop.setInverseJoin(inverseDest); + } + + + + private String errorMsgMissingBeanTable(Class type, String from) { + return "Error with association to ["+type+"] from ["+from+"]. Is "+type+" registered?"; + } + + private void readToMany(ManyToMany propAnn, DeployBeanPropertyAssocMany manyProp) { + + manyProp.setMappedBy(propAnn.mappedBy()); + manyProp.setFetchType(propAnn.fetch()); + + setCascadeTypes(propAnn.cascade(), manyProp.getCascadeInfo()); + + Class targetType = propAnn.targetEntity(); + if (targetType.equals(void.class)) { + // via reflection of generics type + targetType = manyProp.getTargetType(); + } else { + manyProp.setTargetType(targetType); + } + + // find the other many table (not intersection) + BeanTable assoc = factory.getBeanTable(targetType); + if (assoc == null) { + String msg = errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName()); + throw new RuntimeException(msg); + } + + manyProp.setManyToMany(true); + manyProp.setModifyListenMode(ModifyListenMode.ALL); + manyProp.setBeanTable(assoc); + manyProp.getTableJoin().setType(TableJoin.LEFT_OUTER); + } + + private void readToOne(OneToMany propAnn, DeployBeanPropertyAssocMany manyProp) { + + manyProp.setMappedBy(propAnn.mappedBy()); + manyProp.setFetchType(propAnn.fetch()); + + setCascadeTypes(propAnn.cascade(), manyProp.getCascadeInfo()); + + Class targetType = propAnn.targetEntity(); + if (targetType.equals(void.class)) { + // via reflection of generics type + targetType = manyProp.getTargetType(); + } else { + manyProp.setTargetType(targetType); + } + + BeanTable assoc = factory.getBeanTable(targetType); + if (assoc == null) { + String msg = errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName()); + throw new RuntimeException(msg); + } + + manyProp.setBeanTable(assoc); + manyProp.getTableJoin().setType(TableJoin.LEFT_OUTER); + } + + + private String getM2MJoinTableName(BeanTable lhsTable, BeanTable rhsTable){ + + TableName lhs = new TableName(lhsTable.getBaseTable()); + TableName rhs = new TableName(rhsTable.getBaseTable()); + + TableName joinTable = namingConvention.getM2MJoinTableName(lhs, rhs); + + return joinTable.getQualifiedName(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java new file mode 100644 index 000000000..cf6ca1aa2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -0,0 +1,246 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.EmbeddedId; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; + +import com.avaje.ebean.annotation.EmbeddedColumns; +import com.avaje.ebean.annotation.Where; +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebean.validation.NotNull; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeaninternal.server.deploy.BeanTable; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +/** + * Read the deployment annotations for Associated One beans. + */ +public class AnnotationAssocOnes extends AnnotationParser { + + private final BeanDescriptorManager factory; + + /** + * Create with the deploy Info. + */ + public AnnotationAssocOnes(DeployBeanInfo info, BeanDescriptorManager factory) { + super(info); + this.factory = factory; + } + + /** + * Parse the annotation. + */ + public void parse() { + + Iterator it = descriptor.propertiesAll(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop instanceof DeployBeanPropertyAssocOne) { + readAssocOne((DeployBeanPropertyAssocOne) prop); + } + } + } + + private void readAssocOne(DeployBeanPropertyAssocOne prop) { + + ManyToOne manyToOne = get(prop, ManyToOne.class); + if (manyToOne != null) { + readManyToOne(manyToOne, prop); + } + OneToOne oneToOne = get(prop, OneToOne.class); + if (oneToOne != null) { + readOneToOne(oneToOne, prop); + } + Embedded embedded = get(prop, Embedded.class); + if (embedded != null) { + readEmbedded(embedded, prop); + } + EmbeddedId emId = get(prop, EmbeddedId.class); + if (emId != null) { + prop.setEmbedded(true); + prop.setId(true); + prop.setNullable(false); + } + Column column = get(prop, Column.class); + if (column != null && !isEmpty(column.name())) { + // have this in for AssocOnes used on + // Sql based beans... + prop.setDbColumn(column.name()); + } + + // May as well check for Id. Makes sense to me. + Id id = get(prop, Id.class); + if (id != null) { + prop.setEmbedded(true); + prop.setId(true); + prop.setNullable(false); + } + + Where where = get(prop, Where.class); + if (where != null) { + // not expecting this to be used on assoc one properties + prop.setExtraWhere(where.clause()); + } + + NotNull notNull = get(prop, NotNull.class); + if (notNull != null) { + prop.setNullable(false); + // overrides optional attribute of ManyToOne etc + prop.getTableJoin().setType(TableJoin.JOIN); + } + + // check for manually defined joins + BeanTable beanTable = prop.getBeanTable(); + JoinColumn joinColumn = get(prop, JoinColumn.class); + if (joinColumn != null) { + prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable); + if (!joinColumn.updatable()){ + prop.setDbUpdateable(false); + } + } + + JoinColumns joinColumns = get(prop, JoinColumns.class); + if (joinColumns != null) { + prop.getTableJoin().addJoinColumn(false, joinColumns.value(), beanTable); + } + + JoinTable joinTable = get(prop, JoinTable.class); + if (joinTable != null) { + prop.getTableJoin().addJoinColumn(false, joinTable.joinColumns(), beanTable); + } + + info.setBeanJoinType(prop, prop.isNullable()); + + if (!prop.getTableJoin().hasJoinColumns() && beanTable != null) { + + if (prop.getMappedBy() != null) { + // the join is derived by reversing the join information + // from the mapped by property. + // Refer BeanDescriptorManager.readEntityRelationships() + + } else { + // use naming convention to define join. + NamingConvention nc = factory.getNamingConvention(); + + String fkeyPrefix = null; + if (nc.isUseForeignKeyPrefix()){ + fkeyPrefix = nc.getColumnFromProperty(beanType, prop.getName()); + } + + beanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), true); + } + } + } + + private String errorMsgMissingBeanTable(Class type, String from) { + return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered?"; + } + + private void readManyToOne(ManyToOne propAnn, DeployBeanProperty prop) { + + DeployBeanPropertyAssocOne beanProp = (DeployBeanPropertyAssocOne) prop; + + setCascadeTypes(propAnn.cascade(), beanProp.getCascadeInfo()); + + BeanTable assoc = factory.getBeanTable(beanProp.getPropertyType()); + if (assoc == null) { + String msg = errorMsgMissingBeanTable(beanProp.getPropertyType(), prop.getFullBeanName()); + throw new RuntimeException(msg); + } + beanProp.setBeanTable(assoc); + beanProp.setDbInsertable(true); + beanProp.setDbUpdateable(true); + beanProp.setNullable(propAnn.optional()); + beanProp.setFetchType(propAnn.fetch()); + } + + private void readOneToOne(OneToOne propAnn, DeployBeanPropertyAssocOne prop) { + + prop.setOneToOne(true); + prop.setDbInsertable(true); + prop.setDbUpdateable(true); + prop.setNullable(propAnn.optional()); + prop.setFetchType(propAnn.fetch()); + prop.setMappedBy(propAnn.mappedBy()); + if (!"".equals(propAnn.mappedBy())) { + prop.setOneToOneExported(true); + } + + setCascadeTypes(propAnn.cascade(), prop.getCascadeInfo()); + + BeanTable assoc = factory.getBeanTable(prop.getPropertyType()); + if (assoc == null) { + String msg = errorMsgMissingBeanTable(prop.getPropertyType(), prop.getFullBeanName()); + throw new RuntimeException(msg); + } + + prop.setBeanTable(assoc); + } + + private void readEmbedded(Embedded propAnn, DeployBeanPropertyAssocOne prop) { + + prop.setEmbedded(true); + prop.setDbInsertable(true); + prop.setDbUpdateable(true); + + EmbeddedColumns columns = get(prop, EmbeddedColumns.class); + if (columns != null) { + + // convert into a Map + String propColumns = columns.columns(); + Map propMap = StringHelper.delimitedToMap(propColumns, ",", "="); + + prop.getDeployEmbedded().putAll(propMap); + } + + AttributeOverrides attrOverrides = get(prop, AttributeOverrides.class); + if (attrOverrides != null) { + HashMap propMap = new HashMap(); + AttributeOverride[] aoArray = attrOverrides.value(); + for (int i = 0; i < aoArray.length; i++) { + String propName = aoArray[i].name(); + String columnName = aoArray[i].column().name(); + + propMap.put(propName, columnName); + } + + prop.getDeployEmbedded().putAll(propMap); + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java new file mode 100644 index 000000000..e28082ef7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java @@ -0,0 +1,78 @@ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +/** + * Provides some base methods for processing deployment annotations. + */ +public abstract class AnnotationBase { + + protected final DatabasePlatform databasePlatform; + protected final NamingConvention namingConvention; + protected final DeployUtil util; + + protected AnnotationBase(DeployUtil util) { + this.util = util; + this.databasePlatform = util.getDbPlatform(); + this.namingConvention = util.getNamingConvention(); + } + + /** + * read the deployment annotations. + */ + public abstract void parse(); + + /** + * Checks string is null or empty . + */ + protected boolean isEmpty(String s) { + if (s == null || s.trim().length() == 0) { + return true; + } + return false; + } + + + /** + * Return the annotation for the property. + *

+ * Looks first at the field and then at the getter method. + *

+ */ + protected T get(DeployBeanProperty prop, Class annClass) { + T a = null; + Field field = prop.getField(); + if (field != null){ + a = field.getAnnotation(annClass); + } + if (a == null) { + Method m = prop.getReadMethod(); + if (m != null) { + a = m.getAnnotation(annClass); + } + } + return a; + } + + /** + * Return the annotation for the property. + *

+ * Looks first at the field and then at the getter method. then at class level. + *

+ */ + protected T find(DeployBeanProperty prop, Class annClass) { + T a = get(prop, annClass); + if (a == null) { + a = prop.getOwningType().getAnnotation(annClass); + } + return a; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java new file mode 100644 index 000000000..cb8544c1c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import com.avaje.ebean.config.TableName; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanTable; + +/** + * Read the annotations for BeanTable. + *

+ * Refer to BeanTable but basically determining base table, table alias + * and the unique id properties. + *

+ */ +public class AnnotationBeanTable extends AnnotationBase { + + final DeployBeanTable beanTable; + + public AnnotationBeanTable(DeployUtil util, DeployBeanTable beanTable){ + super(util); + this.beanTable = beanTable; + } + + /** + * Parse the annotations. + */ + public void parse() { + + TableName tableName = namingConvention.getTableName(beanTable.getBeanType()); + + beanTable.setBaseTable(tableName.getQualifiedName()); + } +} 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 new file mode 100644 index 000000000..c95eafa19 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -0,0 +1,244 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +import com.avaje.ebean.Query.UseIndex; +import com.avaje.ebean.annotation.CacheStrategy; +import com.avaje.ebean.annotation.LdapDomain; +import com.avaje.ebean.annotation.NamedUpdate; +import com.avaje.ebean.annotation.NamedUpdates; +import com.avaje.ebean.annotation.UpdateMode; +import com.avaje.ebean.config.TableName; +import com.avaje.ebeaninternal.server.core.CacheOptions; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; +import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint; +import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; +import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +/** + * Read the class level deployment annotations. + */ +public class AnnotationClass extends AnnotationParser { + + public AnnotationClass(DeployBeanInfo info) { + super(info); + } + + /** + * Read the class level deployment annotations. + */ + public void parse() { + read(descriptor.getBeanType()); + setTableName(); + } + + /** + * Set the table name if it has not already been set. + */ + private void setTableName() { + + if (descriptor.isBaseTableType()) { + + // default the TableName using NamingConvention. + TableName tableName = namingConvention.getTableName(descriptor.getBeanType()); + + descriptor.setBaseTable(tableName); + } + } + + private String[] parseLdapObjectclasses(String objectclasses) { + + if (objectclasses == null || objectclasses.length() == 0){ + return null; + } + return objectclasses.split(","); + } + + private boolean isXmlElement(Class cls) { + XmlRootElement rootElement = cls.getAnnotation(XmlRootElement.class); + if (rootElement != null){ + return true; + } + XmlType xmlType = cls.getAnnotation(XmlType.class); + if (xmlType != null){ + return true; + } + return false; + } + + private void read(Class cls) { + + LdapDomain ldapDomain = cls.getAnnotation(LdapDomain.class); + if (ldapDomain != null) { + descriptor.setName(cls.getSimpleName()); + descriptor.setEntityType(EntityType.LDAP); + descriptor.setLdapBaseDn(ldapDomain.baseDn()); + descriptor.setLdapObjectclasses(parseLdapObjectclasses(ldapDomain.objectclass())); + } + + Entity entity = cls.getAnnotation(Entity.class); + if (entity != null){ + //checkDefaultConstructor(); + if (entity.name().equals("")) { + descriptor.setName(cls.getSimpleName()); + + } else { + descriptor.setName(entity.name()); + } + } else if (isXmlElement(cls)) { + descriptor.setName(cls.getSimpleName()); + descriptor.setEntityType(EntityType.XMLELEMENT); + } + + Embeddable embeddable = cls.getAnnotation(Embeddable.class); + if (embeddable != null){ + descriptor.setEntityType(EntityType.EMBEDDED); + descriptor.setName("Embeddable:"+cls.getSimpleName()); + } + + UniqueConstraint uc = cls.getAnnotation(UniqueConstraint.class); + if (uc != null){ + descriptor.addCompoundUniqueConstraint(new CompoundUniqueContraint(uc.columnNames())); + } + + Table table = cls.getAnnotation(Table.class); + if (table != null){ + UniqueConstraint[] uniqueConstraints = table.uniqueConstraints(); + if (uniqueConstraints != null){ + for (UniqueConstraint c : uniqueConstraints) { + descriptor.addCompoundUniqueConstraint(new CompoundUniqueContraint(c.columnNames())); + } + } + } + + UpdateMode updateMode = cls.getAnnotation(UpdateMode.class); + if (updateMode != null){ + descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly()); + } + + NamedQueries namedQueries = cls.getAnnotation(NamedQueries.class); + if (namedQueries != null){ + readNamedQueries(namedQueries); + } + NamedQuery namedQuery = cls.getAnnotation(NamedQuery.class); + if (namedQuery != null){ + readNamedQuery(namedQuery); + } + + NamedUpdates namedUpdates = cls.getAnnotation(NamedUpdates.class); + if (namedUpdates != null){ + readNamedUpdates(namedUpdates); + } + + NamedUpdate namedUpdate = cls.getAnnotation(NamedUpdate.class); + if (namedUpdate != null){ + readNamedUpdate(namedUpdate); + } + + CacheStrategy cacheStrategy = cls.getAnnotation(CacheStrategy.class); + if (cacheStrategy != null){ + readCacheStrategy(cacheStrategy); + } + } + + private void readCacheStrategy(CacheStrategy cacheStrategy){ + + CacheOptions cacheOptions = descriptor.getCacheOptions(); + cacheOptions.setUseCache(cacheStrategy.useBeanCache()); + cacheOptions.setReadOnly(cacheStrategy.readOnly()); + cacheOptions.setWarmingQuery(cacheStrategy.warmingQuery()); + if (cacheStrategy.naturalKey().length() > 0){ + String propName = cacheStrategy.naturalKey().trim(); + DeployBeanProperty beanProperty = descriptor.getBeanProperty(propName); + if (beanProperty != null){ + beanProperty.setNaturalKey(true); + cacheOptions.setNaturalKey(propName); + } + } + + if (!UseIndex.DEFAULT.equals(cacheStrategy.useIndex())){ + // a specific text index strategy has been defined + descriptor.setUseIndex(cacheStrategy.useIndex()); + } + } + + private void readNamedQueries(NamedQueries namedQueries) { + NamedQuery[] queries = namedQueries.value(); + for (int i = 0; i < queries.length; i++) { + readNamedQuery(queries[i]); + } + } + + private void readNamedQuery(NamedQuery namedQuery) { + DeployNamedQuery q = new DeployNamedQuery(namedQuery); + descriptor.add(q); + } + + private void readNamedUpdates(NamedUpdates updates) { + NamedUpdate[] updateArray = updates.value(); + for (int i = 0; i < updateArray.length; i++) { + readNamedUpdate(updateArray[i]); + } + } + + private void readNamedUpdate(NamedUpdate update) { + DeployNamedUpdate upd = new DeployNamedUpdate(update); + descriptor.add(upd); + } + +// /** +// * Check to see if the Entity bean has a default constructor. +// *

+// * If it does not then it is expected that this entity bean has an +// * associated BeanFinder. +// *

+// */ +// private void checkDefaultConstructor() { +// +// Class beanType = descriptor.getBeanType(); +// +// Constructor defaultConstructor; +// try { +// defaultConstructor = beanType.getConstructor((Class[]) null); +// if (defaultConstructor == null) { +// String m = "No default constructor on "+beanType; +// throw new PersistenceException(m); +// } +// } catch (SecurityException e) { +// String m = "Error checking for default constructor on "+beanType; +// throw new PersistenceException(m, e); +// +// } catch (NoSuchMethodException e) { +// String m = "No default constructor on "+beanType; +// throw new PersistenceException(m); +// } +// } + +} 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 new file mode 100644 index 000000000..6adeb27af --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -0,0 +1,527 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.sql.Types; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.EmbeddedId; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PersistenceException; +import javax.persistence.SequenceGenerator; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; +import javax.persistence.Version; + +import com.avaje.ebean.annotation.CreatedTimestamp; +import com.avaje.ebean.annotation.EmbeddedColumns; +import com.avaje.ebean.annotation.Encrypted; +import com.avaje.ebean.annotation.Formula; +import com.avaje.ebean.annotation.LdapAttribute; +import com.avaje.ebean.annotation.LdapId; +import com.avaje.ebean.annotation.UpdatedTimestamp; +import com.avaje.ebean.config.EncryptDeploy; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.EncryptDeploy.Mode; +import com.avaje.ebean.config.dbplatform.DbEncrypt; +import com.avaje.ebean.config.dbplatform.DbEncryptFunction; +import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebean.validation.Length; +import com.avaje.ebean.validation.NotNull; +import com.avaje.ebean.validation.Pattern; +import com.avaje.ebean.validation.Patterns; +import com.avaje.ebean.validation.ValidatorMeta; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; +import com.avaje.ebeaninternal.server.idgen.UuidIdGenerator; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.type.CtCompoundType; +import com.avaje.ebeaninternal.server.type.DataEncryptSupport; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.ScalarTypeBytesBase; +import com.avaje.ebeaninternal.server.type.ScalarTypeBytesEncrypted; +import com.avaje.ebeaninternal.server.type.ScalarTypeEncryptedWrapper; +import com.avaje.ebeaninternal.server.type.ScalarTypeLdapBoolean; +import com.avaje.ebeaninternal.server.type.ScalarTypeLdapDate; +import com.avaje.ebeaninternal.server.type.ScalarTypeLdapTimestamp; + +/** + * Read the field level deployment annotations. + */ +public class AnnotationFields extends AnnotationParser { + + /** + * By default we lazy load Lob properties. + */ + private FetchType defaultLobFetchType = FetchType.LAZY; + + private GeneratedPropertyFactory generatedPropFactory = new GeneratedPropertyFactory(); + + public AnnotationFields(DeployBeanInfo info) { + super(info); + + if (GlobalProperties.getBoolean("ebean.lobEagerFetch", false)) { + defaultLobFetchType = FetchType.EAGER; + } + } + + /** + * Read the field level deployment annotations. + */ + public void parse() { + + Iterator it = descriptor.propertiesAll(); + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop instanceof DeployBeanPropertyAssoc) { + readAssocOne(prop); + } else { + readField(prop); + } + + readValidations(prop); + } + } + + /** + * Read the Id marker annotations on EmbeddedId properties. + */ + private void readAssocOne(DeployBeanProperty prop) { + + Id id = get(prop, Id.class); + if (id != null) { + prop.setId(true); + prop.setNullable(false); + } + + EmbeddedId embeddedId = get(prop, EmbeddedId.class); + if (embeddedId != null) { + prop.setId(true); + prop.setNullable(false); + prop.setEmbedded(true); + } + + } + + private void readField(DeployBeanProperty prop) { + + // all Enums will have a ScalarType assigned... + boolean isEnum = prop.getPropertyType().isEnum(); + Enumerated enumerated = get(prop, Enumerated.class); + if (isEnum || enumerated != null) { + util.setEnumScalarType(enumerated, prop); + } + + // its persistent and assumed to be on the base table + // rather than on a secondary table + prop.setDbRead(true); + prop.setDbInsertable(true); + prop.setDbUpdateable(true); + + Column column = get(prop, Column.class); + if (column != null) { + readColumn(column, prop); + } + LdapAttribute ldapAttribute = get(prop, LdapAttribute.class); + if (ldapAttribute != null) { + // read ldap specific property settings + readLdapAttribute(ldapAttribute, prop); + } + + if (prop.getDbColumn() == null){ + if (EntityType.LDAP.equals(descriptor.getEntityType())) { + // just use matching for now. Could consider an LdapNamingConvention later. + prop.setDbColumn(prop.getName()); + } else { + // No @Column annotation or @Column.name() not set + // Use the NamingConvention to set the DB column name + String dbColumn = namingConvention.getColumnFromProperty(beanType, prop.getName()); + prop.setDbColumn(dbColumn); + } + } + + GeneratedValue gen = get(prop, GeneratedValue.class); + if (gen != null) { + readGenValue(gen, prop); + } + + Id id = (Id) get(prop, Id.class); + if (id != null) { + readId(id, prop); + } + LdapId ldapId = (LdapId)get(prop, LdapId.class); + if (ldapId != null) { + prop.setId(true); + prop.setNullable(false); + } + + + // determine the JDBC type using Lob/Temporal + // otherwise based on the property Class + Lob lob = get(prop, Lob.class); + Temporal temporal = get(prop, Temporal.class); + if (temporal != null) { + readTemporal(temporal, prop); + + } else if (lob != null) { + util.setLobType(prop); + } + + Formula formula = get(prop, Formula.class); + if (formula != null) { + prop.setSqlFormula(formula.select(), formula.join()); + } + + Version version = get(prop, Version.class); + if (version != null) { + // explicitly specify a version column + prop.setVersionColumn(true); + generatedPropFactory.setVersion(prop); + } + + Basic basic = get(prop, Basic.class); + if (basic != null) { + prop.setFetchType(basic.fetch()); + if (!basic.optional()) { + prop.setNullable(false); + } + } else if (prop.isLob()){ + // use the default Lob fetchType + prop.setFetchType(defaultLobFetchType); + } + + CreatedTimestamp ct = get(prop, CreatedTimestamp.class); + if (ct != null) { + generatedPropFactory.setInsertTimestamp(prop); + } + + UpdatedTimestamp ut = get(prop, UpdatedTimestamp.class); + if (ut != null) { + generatedPropFactory.setUpdateTimestamp(prop); + } + + NotNull notNull = get(prop, NotNull.class); + if (notNull != null) { + // explicitly specify a version column + prop.setNullable(false); + } + + Length length = get(prop, Length.class); + if (length != null) { + if (length.max() < Integer.MAX_VALUE){ + // explicitly specify a version column + prop.setDbLength(length.max()); + } + } + + EmbeddedColumns columns = get(prop, EmbeddedColumns.class); + if (columns != null) { + if (prop instanceof DeployBeanPropertyCompound){ + DeployBeanPropertyCompound p = (DeployBeanPropertyCompound)prop; + + // convert into a Map + String propColumns = columns.columns(); + Map propMap = StringHelper.delimitedToMap(propColumns, ",", "="); + + p.getDeployEmbedded().putAll(propMap); + + CtCompoundType compoundType = p.getCompoundType(); + if (compoundType == null){ + throw new RuntimeException("No registered CtCompoundType for "+p.getPropertyType()); + } + + } else { + throw new RuntimeException("Can't use EmbeddedColumns on ScalarType "+prop.getFullBeanName()); + } + } + + // Want to process last so we can use with @Formula + Transient t = get(prop, Transient.class); + if (t != null) { + // it is not a persistent property. + prop.setDbRead(false); + prop.setDbInsertable(false); + prop.setDbUpdateable(false); + prop.setTransient(true); + } + + if (!prop.isTransient()){ + + EncryptDeploy encryptDeploy = util.getEncryptDeploy(info.getDescriptor().getBaseTableFull(), prop.getDbColumn()); + if (encryptDeploy == null || encryptDeploy.getMode().equals(Mode.MODE_ANNOTATION)){ + Encrypted encrypted = get(prop, Encrypted.class); + if (encrypted != null) { + setEncryption(prop, encrypted.dbEncryption(), encrypted.dbLength()); + } + } else if (Mode.MODE_ENCRYPT.equals(encryptDeploy.getMode())) { + setEncryption(prop, encryptDeploy.isDbEncrypt(), encryptDeploy.getDbLength()); + } + } + + if (EntityType.LDAP.equals(descriptor.getEntityType())){ + adjustTypesForLdap(prop); + } + } + + private static final ScalarTypeLdapBoolean LDAP_BOOLEAN_SCALARTYPE = new ScalarTypeLdapBoolean(); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void adjustTypesForLdap(DeployBeanProperty prop) { + + Class pt = prop.getPropertyType(); + if (boolean.class.equals(pt) || Boolean.class.equals(pt)){ + prop.setScalarType(LDAP_BOOLEAN_SCALARTYPE); + + } else { + ScalarType sqlScalarType = prop.getScalarType(); + int sqlType = sqlScalarType.getJdbcType(); + if (sqlType == Types.TIMESTAMP){ + // Use LDAP Timestamp String format + prop.setScalarType(new ScalarTypeLdapTimestamp(sqlScalarType)); + + } else if (sqlType == Types.DATE){ + // Use LDAP Timestamp String format + prop.setScalarType(new ScalarTypeLdapDate(sqlScalarType)); + + } else { + // Just using string parsing for all other types + } + } + } + + private void setEncryption(DeployBeanProperty prop, boolean dbEncString, int dbLen) { + + util.checkEncryptKeyManagerDefined(prop.getFullBeanName()); + + ScalarType st = prop.getScalarType(); + if (byte[].class.equals(st.getType())){ + // Always using Java client encryption rather than DB for encryption + // of binary data (partially as this is not supported on all db's etc) + // This could be reviewed at a later stage. + ScalarTypeBytesBase baseType = (ScalarTypeBytesBase)st; + DataEncryptSupport support = createDataEncryptSupport(prop); + ScalarTypeBytesEncrypted encryptedScalarType = new ScalarTypeBytesEncrypted(baseType, support); + prop.setScalarType(encryptedScalarType); + prop.setLocalEncrypted(true); + return; + + } + if (dbEncString){ + + DbEncrypt dbEncrypt = util.getDbPlatform().getDbEncrypt(); + + if (dbEncrypt != null){ + // check if we have a DB encryption function for this type + int jdbcType = prop.getScalarType().getJdbcType(); + DbEncryptFunction dbEncryptFunction = dbEncrypt.getDbEncryptFunction(jdbcType); + if (dbEncryptFunction != null){ + // Use DB functions to encrypt and decrypt + prop.setDbEncryptFunction(dbEncryptFunction, dbEncrypt, dbLen); + return; + } + } + } + + prop.setScalarType(createScalarType(prop, st)); + prop.setLocalEncrypted(true); + if (dbLen > 0){ + prop.setDbLength(dbLen); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private ScalarTypeEncryptedWrapper createScalarType(DeployBeanProperty prop, ScalarType st ) { + + // Use Java Encryptor wrapping the logical scalar type + DataEncryptSupport support = createDataEncryptSupport(prop); + ScalarTypeBytesBase byteType = getDbEncryptType(prop); + + return new ScalarTypeEncryptedWrapper(st, byteType, support); + } + + private ScalarTypeBytesBase getDbEncryptType(DeployBeanProperty prop) { + int dbType = prop.isLob() ? Types.BLOB : Types.VARBINARY; + return (ScalarTypeBytesBase)util.getTypeManager().getScalarType(dbType); + } + + private DataEncryptSupport createDataEncryptSupport(DeployBeanProperty prop) { + + String table = info.getDescriptor().getBaseTable(); + String column = prop.getDbColumn(); + + return util.createDataEncryptSupport(table, column); + } + + + private void readId(Id id, DeployBeanProperty prop) { + + prop.setId(true); + prop.setNullable(false); + + if (prop.getPropertyType().equals(UUID.class)){ + // An Id of type UUID + if (descriptor.getIdGeneratorName() == null){ + // Without a generator explicitly specified + // so will use the default one AUTO_UUID + descriptor.setIdGeneratorName(UuidIdGenerator.AUTO_UUID); + descriptor.setIdType(IdType.GENERATOR); + } + } + } + + private void readGenValue(GeneratedValue gen, DeployBeanProperty prop) { + + String genName = gen.generator(); + + SequenceGenerator sequenceGenerator = find(prop, SequenceGenerator.class); + if (sequenceGenerator != null) { + if (sequenceGenerator.name().equals(genName)) { + genName = sequenceGenerator.sequenceName(); + } + } + + GenerationType strategy = gen.strategy(); + + if (strategy == GenerationType.IDENTITY) { + descriptor.setIdType(IdType.IDENTITY); + + } else if (strategy == GenerationType.SEQUENCE) { + descriptor.setIdType(IdType.SEQUENCE); + if (genName != null && genName.length() > 0) { + descriptor.setIdGeneratorName(genName); + } + + } else if (strategy == GenerationType.AUTO) { + if (prop.getPropertyType().equals(UUID.class)){ + descriptor.setIdGeneratorName(UuidIdGenerator.AUTO_UUID); + descriptor.setIdType(IdType.GENERATOR); + + } else { + // use DatabasePlatform defaults + } + } + } + + private void readTemporal(Temporal temporal, DeployBeanProperty prop) { + + TemporalType type = temporal.value(); + if (type.equals(TemporalType.DATE)) { + prop.setDbType(Types.DATE); + + } else if (type.equals(TemporalType.TIMESTAMP)) { + prop.setDbType(Types.TIMESTAMP); + + } else if (type.equals(TemporalType.TIME)) { + prop.setDbType(Types.TIME); + + } else { + throw new PersistenceException("Unhandled type " + type); + } + } + + + private void readColumn(Column columnAnn, DeployBeanProperty prop) { + + if (!isEmpty(columnAnn.name())){ + String dbColumn = databasePlatform.convertQuotedIdentifiers(columnAnn.name()); + prop.setDbColumn(dbColumn); + } + + prop.setDbInsertable(columnAnn.insertable()); + prop.setDbUpdateable(columnAnn.updatable()); + prop.setNullable(columnAnn.nullable()); + prop.setUnique(columnAnn.unique()); + if (columnAnn.precision() > 0){ + prop.setDbLength(columnAnn.precision()); + } else if (columnAnn.length() != 255){ + // set default 255 on DbTypeMap + prop.setDbLength(columnAnn.length()); + } + prop.setDbScale(columnAnn.scale()); + prop.setDbColumnDefn(columnAnn.columnDefinition()); + + String baseTable = descriptor.getBaseTable(); + String tableName = columnAnn.table(); + if (tableName.equals("") || tableName.equalsIgnoreCase(baseTable)) { + // its a base table property... + } else { + // its on a secondary table... + prop.setSecondaryTable(tableName); + //DeployTableJoin tableJoin = info.getTableJoin(tableName); + //tableJoin.addProperty(prop); + } + } + + + + private void readValidations(DeployBeanProperty prop) { + + Field field = prop.getField(); + if (field != null) { + Annotation[] fieldAnnotations = field.getAnnotations(); + for (int i = 0; i < fieldAnnotations.length; i++) { + readValidations(prop, fieldAnnotations[i]); + } + } + + Method readMethod = prop.getReadMethod(); + if (readMethod != null) { + Annotation[] methAnnotations = readMethod.getAnnotations(); + for (int i = 0; i < methAnnotations.length; i++) { + readValidations(prop, methAnnotations[i]); + } + } + } + + private void readValidations(DeployBeanProperty prop, Annotation ann) { + Class type = ann.annotationType(); + if (type.equals(Patterns.class)){ + // treating this as a special case for now... + Patterns patterns = (Patterns)ann; + Pattern[] patternsArray = patterns.patterns(); + for (int i = 0; i < patternsArray.length; i++) { + util.createValidator(prop, patternsArray[i]); + } + + } else { + + ValidatorMeta meta = type.getAnnotation(ValidatorMeta.class); + if (meta != null) { + util.createValidator(prop, ann); + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java new file mode 100644 index 000000000..92011c95f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import javax.persistence.CascadeType; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.annotation.LdapAttribute; +import com.avaje.ebean.config.ldap.LdapAttributeAdapter; +import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +/** + * Base class for reading deployment annotations. + */ +public abstract class AnnotationParser extends AnnotationBase { + + protected final DeployBeanInfo info; + + protected final DeployBeanDescriptor descriptor; + + protected final Class beanType; + + public AnnotationParser(DeployBeanInfo info){ + super(info.getUtil()); + this.info = info; + this.beanType = info.getDescriptor().getBeanType(); + this.descriptor = info.getDescriptor(); + } + + /** + * read the deployment annotations. + */ + public abstract void parse(); + + /** + * Helper method to set cascade types to the CascadeInfo on BeanProperty. + */ + protected void setCascadeTypes(CascadeType[] cascadeTypes, BeanCascadeInfo cascadeInfo) { + if (cascadeTypes != null && cascadeTypes.length > 0) { + cascadeInfo.setTypes(cascadeTypes); + } + } + + protected void readLdapAttribute(LdapAttribute ldapAttribute, DeployBeanProperty prop) { + + if (!isEmpty(ldapAttribute.name())){ + prop.setDbColumn(ldapAttribute.name()); + } + prop.setDbInsertable(ldapAttribute.insertable()); + prop.setDbUpdateable(ldapAttribute.updatable()); + + Class adapterCls = ldapAttribute.adapter(); + + if (adapterCls != null && !void.class.equals(adapterCls)){ + try { + LdapAttributeAdapter adapter = (LdapAttributeAdapter)adapterCls.newInstance(); + prop.setLdapAttributeAdapter(adapter); + } catch (Exception e){ + String msg= "Error creating LdapAttributeAdapter for ["+prop.getFullBeanName()+"] " + +"with class ["+adapterCls+"] using the default constructor."; + throw new PersistenceException(msg, e); + } + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationSql.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationSql.java new file mode 100644 index 000000000..b524439eb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationSql.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import com.avaje.ebean.annotation.Sql; +import com.avaje.ebean.annotation.SqlSelect; +import com.avaje.ebeaninternal.server.deploy.DRawSqlMeta; + +/** + * Read the class level deployment annotations. + */ +public class AnnotationSql extends AnnotationParser { + + public AnnotationSql(DeployBeanInfo info) { + super(info); + } + + public void parse() { + Class cls = descriptor.getBeanType(); + Sql sql = cls.getAnnotation(Sql.class); + if (sql != null){ + setSql(sql); + } + + + SqlSelect sqlSelect = cls.getAnnotation(SqlSelect.class); + if (sqlSelect != null){ + setSqlSelect(sqlSelect); + } + } + + private void setSql(Sql sql) { + SqlSelect[] select = sql.select(); + for (int i = 0; i < select.length; i++) { + setSqlSelect(select[i]); + } + } + + private void setSqlSelect(SqlSelect sqlSelect) { + + DRawSqlMeta rawSqlMeta = new DRawSqlMeta(sqlSelect); + descriptor.add(rawSqlMeta); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java new file mode 100644 index 000000000..497818650 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployBeanInfo.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.util.HashMap; + +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin; + +/** + * Wraps information about a bean during deployment parsing. + */ +public class DeployBeanInfo { + + /** + * Holds TableJoins for secondary table properties. + */ + private final HashMap tableJoinMap = new HashMap(); + + private final DeployUtil util; + + private final DeployBeanDescriptor descriptor; + + /** + * Create with a DeployUtil and BeanDescriptor. + */ + public DeployBeanInfo(DeployUtil util, DeployBeanDescriptor descriptor) { + this.util = util; + this.descriptor = descriptor; + } + + public String toString() { + return ""+descriptor; + } + + /** + * Return the BeanDescriptor currently being processed. + */ + public DeployBeanDescriptor getDescriptor() { + return descriptor; + } + + /** + * Return the DeployUtil we are using. + */ + public DeployUtil getUtil() { + return util; + } + + /** + * Appropriate TableJoin for a property mapped to a secondary table. + */ + public DeployTableJoin getTableJoin(String tableName) { + + String key = tableName.toLowerCase(); + + DeployTableJoin tableJoin = (DeployTableJoin) tableJoinMap.get(key); + if (tableJoin == null) { + tableJoin = new DeployTableJoin(); + tableJoin.setTable(tableName); + tableJoin.setType(TableJoin.JOIN); + descriptor.addTableJoin(tableJoin); + + tableJoinMap.put(key, tableJoin); + } + return tableJoin; + } + + /** + * Set a the join alias for a assoc one property. + */ + public void setBeanJoinType(DeployBeanPropertyAssocOne beanProp, boolean outerJoin) { + + String joinType = TableJoin.JOIN; + if (outerJoin){// && util.isUseOneToOneOptional()) { + joinType = TableJoin.LEFT_OUTER; + } + + DeployTableJoin tableJoin = beanProp.getTableJoin(); + tableJoin.setType(joinType); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java new file mode 100644 index 000000000..41d6d2060 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java @@ -0,0 +1,435 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; +import javax.persistence.Transient; + +import com.avaje.ebean.config.ScalarTypeConverter; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.deploy.DetermineManyType; +import com.avaje.ebeaninternal.server.deploy.ManyType; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection; +import com.avaje.ebeaninternal.server.type.CtCompoundType; +import com.avaje.ebeaninternal.server.type.ScalaOptionTypeConverter; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.TypeManager; +import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse; + +/** + * Create the properties for a bean. + *

+ * This also needs to determine if the property is a associated many, associated + * one or normal scalar property. + *

+ */ +public class DeployCreateProperties { + + private static final Logger logger = Logger.getLogger(DeployCreateProperties.class.getName()); + + private final Class scalaOptionClass; + /** + * Use to wrap and unwrap Scala Option. + */ + @SuppressWarnings("rawtypes") + private final ScalarTypeConverter scalaOptionTypeConverter; + + private final DetermineManyType determineManyType; + + private final TypeManager typeManager; + + @SuppressWarnings("rawtypes") + public DeployCreateProperties(TypeManager typeManager) { + this.typeManager = typeManager; + + Class tmpOptionClass = DetectScala.getScalaOptionClass(); + + if (tmpOptionClass == null){ + scalaOptionClass = null; + scalaOptionTypeConverter = null; + } else { + scalaOptionClass = tmpOptionClass; + scalaOptionTypeConverter = new ScalaOptionTypeConverter(); + } + + this.determineManyType = new DetermineManyType(tmpOptionClass != null); + } + + /** + * Create the appropriate properties for a bean. + */ + public void createProperties(DeployBeanDescriptor desc) { + + createProperties(desc, desc.getBeanType(), 0); + desc.sortProperties(); + + // check the transient properties... + Iterator it = desc.propertiesAll(); + + while (it.hasNext()) { + DeployBeanProperty prop = it.next(); + if (prop.isTransient()){ + if (prop.getWriteMethod() == null || prop.getReadMethod() == null){ + // Typically a helper method ... this is expected + logger.finest("... transient: "+prop.getFullBeanName()); + } else { + // dubious, possible error... + String msg = Message.msg("deploy.property.nofield", desc.getFullName(), prop.getName()); + logger.warning(msg); + } + } + } + } + + /** + * Return true if we should ignore this field. + *

+ * We want to ignore ebean internal fields and some others as well. + *

+ */ + private boolean ignoreFieldByName(String fieldName) { + if (fieldName.startsWith("_ebean_")){ + // ignore Ebean internal fields + return true; + } + if (fieldName.startsWith("ajc$instance$")) { + // ignore AspectJ internal fields + return true; + } + + // we are interested in this field + return false; + } + + /** + * reflect the bean properties from Class. Some of these properties may not + * map to database columns. + */ + private void createProperties(DeployBeanDescriptor desc, Class beanType, int level) { + + boolean scalaObject = desc.isScalaObject(); + + try { + Method[] declaredMethods = beanType.getDeclaredMethods(); + Field[] fields = beanType.getDeclaredFields(); + + for (int i = 0; i < fields.length; i++) { + + Field field = fields[i]; + if (Modifier.isStatic(field.getModifiers())) { + // not interested in static fields + + } else if (Modifier.isTransient(field.getModifiers())) { + // not interested in transient fields + logger.finer("Skipping transient field "+field.getName()+" in "+beanType.getName()); + + } else if (ignoreFieldByName(field.getName())) { + // not interested this field (ebean or aspectJ field) + + } else { + + String fieldName = getFieldName(field, beanType); + String initFieldName = initCap(fieldName); + + Method getter = findGetter(field, initFieldName, declaredMethods, scalaObject); + Method setter = findSetter(field, initFieldName, declaredMethods, scalaObject); + + DeployBeanProperty prop = createProp(level, desc, field, beanType, getter, setter); + if (prop == null){ + // transient annotation on unsupported type + + } else { + // set a order that gives priority to inherited properties + // push Id/EmbeddedId up and CreatedTimestamp/UpdatedTimestamp down + int sortOverride = prop.getSortOverride(); + prop.setSortOrder((level*10000+100-i + sortOverride)); + + DeployBeanProperty replaced = desc.addBeanProperty(prop); + if (replaced != null){ + if (replaced.isTransient()) { + // expected for inheritance... + } else { + String msg = "Huh??? property "+prop.getFullBeanName()+" being defined twice"; + msg += " but replaced property was not transient? This is not expected?"; + logger.warning(msg); + } + } + } + } + } + + Class superClass = beanType.getSuperclass(); + + if (!superClass.equals(Object.class)) { + // recursively add any properties in the inheritance heirarchy + // up to the Object.class level... + createProperties(desc, superClass, level + 1); + } + + } catch (PersistenceException ex) { + throw ex; + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + /** + * Make the first letter of the string upper case. + */ + private String initCap(String str){ + if (str.length() > 1){ + return Character.toUpperCase(str.charAt(0))+str.substring(1); + } else { + // only a single char + return str.toUpperCase(); + } + } + + /** + * Return the bean spec field name (trim of "is" from boolean types) + */ + private String getFieldName(Field field, Class beanType){ + + String name = field.getName(); + + if ((Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType())) + && name.startsWith("is") && name.length() > 2){ + + // it is a boolean type field starting with "is" + char c = name.charAt(2); + if (Character.isUpperCase(c)){ + String msg = "trimming off 'is' from boolean field name "+name+" in class "+beanType.getName(); + logger.log(Level.INFO, msg); + + return name.substring(2); + } + } + return name; + } + + /** + * Find a public non-static getter method that matches this field (according to bean-spec rules). + */ + private Method findGetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject){ + + String methGetName = "get"+initFieldName; + String methIsName = "is"+initFieldName; + String scalaGet = field.getName(); + + for (int i = 0; i < declaredMethods.length; i++) { + Method m = declaredMethods[i]; + if ((scalaObject && m.getName().equals(scalaGet)) + || m.getName().equals(methGetName) || m.getName().equals(methIsName)){ + + Class[] params = m.getParameterTypes(); + if (params.length == 0){ + if (field.getType().equals(m.getReturnType())){ + int modifiers = m.getModifiers(); + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { + // we find it... + return m; + } + } + } + } + } + return null; + } + + /** + * Find a public non-static setter method that matches this field (according to bean-spec rules). + */ + private Method findSetter(Field field, String initFieldName, Method[] declaredMethods, boolean scalaObject){ + + String methSetName = "set"+initFieldName; + String scalaSetName = field.getName()+"_$eq"; + + for (int i = 0; i < declaredMethods.length; i++) { + Method m = declaredMethods[i]; + + if ((scalaObject && m.getName().equals(scalaSetName)) + || m.getName().equals(methSetName)){ + + Class[] params = m.getParameterTypes(); + if (params.length == 1 && field.getType().equals(params[0])){ + if (void.class.equals(m.getReturnType())){ + int modifiers = m.getModifiers(); + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { + return m; + } + } + } + } + } + return null; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private DeployBeanProperty createManyType(DeployBeanDescriptor desc, Class targetType, ManyType manyType) { + + ScalarType scalarType = typeManager.getScalarType(targetType); + if (scalarType != null) { + return new DeployBeanPropertySimpleCollection(desc, targetType, scalarType, manyType); + } + //TODO: Handle Collection of CompoundType and Embedded Type + return new DeployBeanPropertyAssocMany(desc, targetType, manyType); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private DeployBeanProperty createProp(DeployBeanDescriptor desc, Field field) { + + Class propertyType = field.getType(); + Class innerType = propertyType; + ScalarTypeConverter typeConverter = null; + + if (propertyType.equals(scalaOptionClass)){ + innerType = determineTargetType(field); + typeConverter = scalaOptionTypeConverter; + } + + // check for Collection type (list, set or map) + ManyType manyType = determineManyType.getManyType(propertyType); + + if (manyType != null) { + // List, Set or Map based object + Class targetType = determineTargetType(field); + if (targetType == null){ + Transient transAnnotation = field.getAnnotation(Transient.class); + if (transAnnotation != null) { + // not supporting this field (generic type used) + return null; + } + logger.warning("Could not find parameter type (via reflection) on "+desc.getFullName()+" "+field.getName()); + } + return createManyType(desc, targetType, manyType); + } + + if (innerType.isEnum() || innerType.isPrimitive()){ + return new DeployBeanProperty(desc, propertyType, null, typeConverter); + } + + ScalarType scalarType = typeManager.getScalarType(innerType); + if (scalarType != null) { + return new DeployBeanProperty(desc, propertyType, scalarType, typeConverter); + } + + CtCompoundType compoundType = typeManager.getCompoundType(innerType); + if (compoundType != null) { + return new DeployBeanPropertyCompound(desc, propertyType, compoundType, typeConverter); + } + + if (!isTransientField(field)){ + try { + CheckImmutableResponse checkImmutable = typeManager.checkImmutable(innerType); + if (checkImmutable.isImmutable()){ + if (checkImmutable.isCompoundType()){ + // use reflection to support compound immutable value objects + typeManager.recursiveCreateScalarDataReader(innerType); + compoundType = typeManager.getCompoundType(innerType); + if (compoundType != null) { + return new DeployBeanPropertyCompound(desc, propertyType, compoundType, typeConverter); + } + + } else { + // use reflection to support simple immutable value objects + scalarType = typeManager.recursiveCreateScalarTypes(innerType); + return new DeployBeanProperty(desc, propertyType, scalarType, typeConverter); + } + } + } catch (Exception e){ + logger.log(Level.SEVERE, "Error with "+desc+" field:"+field.getName(), e); + } + } + + return new DeployBeanPropertyAssocOne(desc, propertyType); + } + + private boolean isTransientField(Field field) { + + Transient t = field.getAnnotation(Transient.class); + return (t != null); + } + + private DeployBeanProperty createProp(int level, DeployBeanDescriptor desc, Field field, Class beanType, Method getter, Method setter) { + + DeployBeanProperty prop = createProp(desc, field); + if (prop == null){ + // transient annotation on unsupported type + return null; + } else { + prop.setOwningType(beanType); + prop.setName(field.getName()); + + // the getter or setter could be null if we are using + // javaagent type enhancement. If we are using subclass + // generation then we do need to find the getter and setter + prop.setReadMethod(getter); + prop.setWriteMethod(setter); + prop.setField(field); + return prop; + } + } + + /** + * Determine the type of the List,Set or Map. Not been set explicitly so + * determine this from ParameterizedType. + */ + private Class determineTargetType(Field field) { + + Type genType = field.getGenericType(); + if (genType instanceof ParameterizedType) { + ParameterizedType ptype = (ParameterizedType) genType; + + Type[] typeArgs = ptype.getActualTypeArguments(); + if (typeArgs.length == 1) { + // probably a Set or List + if (typeArgs[0] instanceof Class){ + return (Class) typeArgs[0]; + } + throw new RuntimeException("Unexpected Parameterised Type? "+typeArgs[0]); + } + if (typeArgs.length == 2) { + // this is probably a Map + if (typeArgs[1] instanceof ParameterizedType) { + // not supporting ParameterizedType on Map. + return null; + } + return (Class) typeArgs[1]; + } + } + // if targetType is null, then must be set in annotations + return null; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInherit.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInherit.java new file mode 100644 index 000000000..bda0809bf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInherit.java @@ -0,0 +1,196 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.lang.annotation.Annotation; +import java.sql.Types; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Inheritance; + +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; + +/** + * Builds the InheritInfo deployment information. + */ +public class DeployInherit { + + private final Map, DeployInheritInfo> deployMap = new LinkedHashMap, DeployInheritInfo>(); + + private final Map, InheritInfo> finalMap = new LinkedHashMap, InheritInfo>(); + + private final BootupClasses bootupClasses; + + /** + * Create the InheritInfoDeploy. + */ + public DeployInherit(BootupClasses bootupClasses) { + this.bootupClasses = bootupClasses; + initialise(); + } + + public void process(DeployBeanDescriptor desc) { + InheritInfo inheritInfo = finalMap.get(desc.getBeanType()); + desc.setInheritInfo(inheritInfo); + } + + private void initialise() { + List> entityList = bootupClasses.getEntities(); + + findInheritClasses(entityList); + buildDeployTree(); + buildFinalTree(); + } + + private void findInheritClasses(List> entityList) { + + // go through each class and initialise the info object... + Iterator> it = entityList.iterator(); + while (it.hasNext()) { + Class cls = (Class) it.next(); + if (isInheritanceClass(cls)) { + DeployInheritInfo info = createInfo(cls); + deployMap.put(cls, info); + } + } + } + + private void buildDeployTree() { + Iterator it = deployMap.values().iterator(); + while (it.hasNext()) { + DeployInheritInfo info = it.next(); + if (!info.isRoot()) { + DeployInheritInfo parent = getInfo(info.getParent()); + parent.addChild(info); + } + } + } + + private void buildFinalTree() { + + Iterator it = deployMap.values().iterator(); + while (it.hasNext()) { + DeployInheritInfo deploy = it.next(); + if (deploy.isRoot()) { + // build tree top down... + createFinalInfo(null, null, deploy); + + } + } + } + + private InheritInfo createFinalInfo(InheritInfo root, InheritInfo parent, + DeployInheritInfo deploy) { + + InheritInfo node = new InheritInfo(root, parent, deploy); + if (parent != null) { + parent.addChild(node); + } + finalMap.put(node.getType(), node); + + if (root == null) { + root = node; + } + + // buildFinalChildren(root, child, deploy); + + Iterator it = deploy.children(); + + while (it.hasNext()) { + DeployInheritInfo childDeploy = it.next(); + + createFinalInfo(root, node, childDeploy); + } + + return node; + } + + /** + * Build the InheritInfo for a given class. + */ + private DeployInheritInfo getInfo(Class cls) { + return deployMap.get(cls); + } + + private DeployInheritInfo createInfo(Class cls) { + + DeployInheritInfo info = new DeployInheritInfo(cls); + + Class parent = findParent(cls); + if (parent != null) { + info.setParent(parent); + } else { + // its the root of inheritance tree... + } + + Inheritance ia = (Inheritance) cls.getAnnotation(Inheritance.class); + if (ia != null) { + ia.strategy(); + } + DiscriminatorColumn da = (DiscriminatorColumn) cls.getAnnotation(DiscriminatorColumn.class); + if (da != null) { + info.setDiscriminatorColumn(da.name()); + DiscriminatorType discriminatorType = da.discriminatorType(); + if (discriminatorType.equals(DiscriminatorType.INTEGER)){ + info.setDiscriminatorType(Types.INTEGER); + } else { + info.setDiscriminatorType(Types.VARCHAR); + } + info.setDiscriminatorLength(da.length()); + } + + DiscriminatorValue dv = (DiscriminatorValue) cls.getAnnotation(DiscriminatorValue.class); + if (dv != null) { + info.setDiscriminatorValue(dv.value()); + } + + return info; + } + + private Class findParent(Class cls) { + Class superCls = cls.getSuperclass(); + if (isInheritanceClass(superCls)) { + return superCls; + } else { + return null; + } + } + + private boolean isInheritanceClass(Class cls) { + if (cls.equals(Object.class)) { + return false; + } + Annotation a = cls.getAnnotation(Inheritance.class); + if (a != null) { + return true; + } + // search up the inheritance heirarchy + return isInheritanceClass(cls.getSuperclass()); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInheritInfo.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInheritInfo.java new file mode 100644 index 000000000..e885dd36d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployInheritInfo.java @@ -0,0 +1,283 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.sql.Types; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.InheritInfo; + +/** + * Represents a node in the Inheritance tree. + * Holds information regarding Super Subclass support. + */ +public class DeployInheritInfo { + + /** + * the default discriminator column according to the JPA 1.0 spec. + */ + private static final String JPA_DEFAULT_DISCRIM_COLUMN = "dtype"; + + private int discriminatorLength; + + private int discriminatorType; + + private String discriminatorStringValue; + private Object discriminatorObjectValue; + + private String discriminatorColumn; + + private String discriminatorWhere; + + private Class type; + + private Class parent; + + private ArrayList children = new ArrayList(); + + /** + * Create for a given type. + */ + public DeployInheritInfo(Class type){ + this.type = type; + } + + /** + * return the type. + */ + public Class getType() { + return type; + } + + /** + * Return the type of the root object. + */ + public Class getParent() { + return parent; + } + + /** + * Set the type of the root object. + */ + public void setParent(Class parent) { + this.parent = parent; + } + + /** + * Return true if this is abstract node. + */ + public boolean isAbstract() { + return (discriminatorObjectValue == null); + } + + /** + * Return true if this is the root node. + */ + public boolean isRoot(){ + return parent == null; + } + + /** + * Return the child nodes. + */ + public Iterator children() { + return children.iterator(); + } + + /** + * Add a child node. + */ + public void addChild(DeployInheritInfo childInfo){ + children.add(childInfo); + } + + /** + * Return the derived where for the discriminator. + */ + public String getDiscriminatorWhere() { + return discriminatorWhere; + } + + /** + * Set the derived where for the discriminator. + */ + public void setDiscriminatorWhere(String discriminatorWhere) { + this.discriminatorWhere = discriminatorWhere; + } + + /** + * Return the column name of the discriminator. + */ + public String getDiscriminatorColumn(InheritInfo parent) { + if (discriminatorColumn == null){ + if (parent == null){ + discriminatorColumn = JPA_DEFAULT_DISCRIM_COLUMN; + } else { + discriminatorColumn = parent.getDiscriminatorColumn(); + } + } + return discriminatorColumn; + } + + /** + * Set the column name of the discriminator. + */ + public void setDiscriminatorColumn(String discriminatorColumn) { + this.discriminatorColumn = discriminatorColumn; + } + + public int getDiscriminatorLength(InheritInfo parent) { + if (discriminatorLength == 0){ + if (parent == null){ + discriminatorLength = 10; + } else { + discriminatorLength = parent.getDiscriminatorLength(); + } + } + return discriminatorLength; + } + + /** + * Return the sql type of the discriminator value. + */ + public int getDiscriminatorType(InheritInfo parent) { + if (discriminatorType == 0){ + if (parent == null){ + discriminatorType = Types.VARCHAR; + } else { + discriminatorType = parent.getDiscriminatorType(); + } + } + return discriminatorType; + } + + /** + * Set the sql type of the discriminator. + */ + public void setDiscriminatorType(int discriminatorType) { + this.discriminatorType = discriminatorType; + } + + /** + * Return the length of the discriminator column. + */ + public int getDiscriminatorLength() { + return discriminatorLength; + } + + /** + * Set the length of the discriminator column. + */ + public void setDiscriminatorLength(int discriminatorLength) { + this.discriminatorLength = discriminatorLength; + } + + /** + * Return the discriminator value for this node. + */ + public Object getDiscriminatorObjectValue() { + return discriminatorObjectValue; + } + + public String getDiscriminatorStringValue() { + return discriminatorStringValue; + } + + /** + * Set the discriminator value for this node. + */ + public void setDiscriminatorValue(String value) { + if (value != null){ + value = value.trim(); + if (value.length() == 0){ + value = null; + } else { + discriminatorStringValue = value; + // convert the value if desired + if (discriminatorType == Types.INTEGER){ + this.discriminatorObjectValue = Integer.valueOf(value.toString()); + } else { + this.discriminatorObjectValue = value; + } + } + } + } + + public String getWhere() { + + List discList = new ArrayList(); + + appendDiscriminator(discList); + + return buildWhereLiteral(discList); + } + + private void appendDiscriminator(List list) { + if (discriminatorObjectValue != null){ + list.add(discriminatorObjectValue); + } + for (DeployInheritInfo child : children) { + child.appendDiscriminator(list); + } + } + + private String buildWhereLiteral(List discList) { + int size = discList.size(); + if (size == 0){ + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append(discriminatorColumn); + if (size == 1){ + sb.append(" = "); + } else { + sb.append(" in ("); + } + for (int i = 0; i < discList.size(); i++) { + appendSqlLiteralValue(i, discList.get(i), sb); + } + if (size > 1){ + sb.append(")"); + } + return sb.toString(); + } + + private void appendSqlLiteralValue(int count, Object value, StringBuilder sb) { + if (count > 0){ + sb.append(","); + } + if (value instanceof String){ + sb.append("'").append(value).append("'"); + } else { + sb.append(value); + } + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("InheritInfo[").append(type.getName()).append("]"); + sb.append(" root[").append(parent.getName()).append("]"); + sb.append(" disValue[").append(discriminatorStringValue).append("]"); + return sb.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java new file mode 100644 index 000000000..b122e1e2b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -0,0 +1,266 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.lang.annotation.Annotation; +import java.sql.Types; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.config.EncryptDeploy; +import com.avaje.ebean.config.EncryptDeployManager; +import com.avaje.ebean.config.EncryptKeyManager; +import com.avaje.ebean.config.Encryptor; +import com.avaje.ebean.config.NamingConvention; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.TableName; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.validation.factory.Validator; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; +import com.avaje.ebeaninternal.server.type.DataEncryptSupport; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.ScalarTypeEnumStandard; +import com.avaje.ebeaninternal.server.type.SimpleAesEncryptor; +import com.avaje.ebeaninternal.server.type.TypeManager; + +/** + * Utility object to help processing deployment information. + */ +public class DeployUtil { + + private static final Logger logger = Logger.getLogger(DeployUtil.class.getName()); + + + + /** + * Assumes CLOB rather than LONGVARCHAR. + */ + private static final int dbCLOBType = Types.CLOB; + + /** + * Assumes BLOB rather than LONGVARBINARY. This should probably be + * configurable. + */ + private static final int dbBLOBType = Types.BLOB; + + private final NamingConvention namingConvention; + + private final TypeManager typeManager; + + private final ValidatorFactoryManager validatorFactoryManager; + + private final String manyToManyAlias; + + private final DatabasePlatform dbPlatform; + + private final EncryptDeployManager encryptDeployManager; + + private final EncryptKeyManager encryptKeyManager; + + private final Encryptor bytesEncryptor; + + public DeployUtil(TypeManager typeMgr, ServerConfig serverConfig) { + + this.typeManager = typeMgr; + this.namingConvention = serverConfig.getNamingConvention(); + this.dbPlatform = serverConfig.getDatabasePlatform(); + this.encryptDeployManager = serverConfig.getEncryptDeployManager(); + this.encryptKeyManager = serverConfig.getEncryptKeyManager(); + + Encryptor be = serverConfig.getEncryptor(); + this.bytesEncryptor = be != null ? be : new SimpleAesEncryptor(); + + // this alias is used for ManyToMany lazy loading queries + this.manyToManyAlias = "zzzzzz"; + + this.validatorFactoryManager = new ValidatorFactoryManager(); + } + + public TypeManager getTypeManager() { + return typeManager; + } + + public DatabasePlatform getDbPlatform() { + return dbPlatform; + } + + public NamingConvention getNamingConvention() { + return namingConvention; + } + + /** + * Check that the EncryptKeyManager has been defined. + */ + public void checkEncryptKeyManagerDefined(String fullPropName) { + if (encryptKeyManager == null){ + String msg = "Using encryption on "+fullPropName+" but no EncryptKeyManager defined!"; + throw new PersistenceException(msg); + } + } + + public EncryptDeploy getEncryptDeploy(TableName table, String column) { + if (encryptDeployManager == null){ + return EncryptDeploy.ANNOTATION; + } + return encryptDeployManager.getEncryptDeploy(table, column); + } + + public DataEncryptSupport createDataEncryptSupport(String table, String column) { + return new DataEncryptSupport(encryptKeyManager, bytesEncryptor, table, column); + } + + /** + * Return the table alias used for ManyToMany joins. + */ + public String getManyToManyAlias() { + return manyToManyAlias; + } + + public void createValidator(DeployBeanProperty prop, Annotation ann) { + try { + Validator validator = validatorFactoryManager.create(ann, prop.getPropertyType()); + if (validator != null){ + prop.addValidator(validator); + } + } catch (Exception e){ + String msg = "Error creating a validator on "+prop.getFullBeanName(); + logger.log(Level.SEVERE, msg, e); + } + } + + public ScalarType setEnumScalarType(Enumerated enumerated, DeployBeanProperty prop) { + + Class enumType = prop.getPropertyType(); + if (!enumType.isEnum()) { + throw new IllegalArgumentException("Class ["+enumType+"] is Not a Enum?"); + } + ScalarType scalarType = typeManager.getScalarType(enumType); + if (scalarType == null) { + // see if it has a Mapping in avaje.properties + scalarType = typeManager.createEnumScalarType(enumType); + if (scalarType == null){ + // use JPA normal Enum type (without mapping) + EnumType type = enumerated != null? enumerated.value(): null; + scalarType = createEnumScalarTypePerSpec(enumType, type, prop.getDbType()); + } + + typeManager.add(scalarType); + } + prop.setScalarType(scalarType); + prop.setDbType(scalarType.getJdbcType()); + return scalarType; + } + + private ScalarType createEnumScalarTypePerSpec(Class enumType, EnumType type, int dbType) { + + if (type == null) { + // default as per spec is ORDINAL + return new ScalarTypeEnumStandard.OrdinalEnum(enumType); + + } else if (type == EnumType.ORDINAL) { + return new ScalarTypeEnumStandard.OrdinalEnum(enumType); + + } else { + return new ScalarTypeEnumStandard.StringEnum(enumType); + } + } + + /** + * Find the ScalarType for this property. + *

+ * This determines if there is a conversion required from the logical (bean) + * type to a DB (jdbc) type. This is the case for java.util.Date etc. + *

+ */ + public void setScalarType(DeployBeanProperty property) { + + if (property.getScalarType() != null){ + // already has a ScalarType assigned. + // this will be an Enum type... + return; + } + if (property instanceof DeployBeanPropertyCompound){ + // compound properties have a CvoInternalType instead + return; + } + + ScalarType scalarType = getScalarType(property); + if (scalarType != null){ + // set the jdbc type this maps to + + property.setDbType(scalarType.getJdbcType()); + property.setScalarType(scalarType); + } + } + + private ScalarType getScalarType(DeployBeanProperty property) { + + // Note that Temporal types already have dbType + // set via annotations + Class propType = property.getPropertyType(); + ScalarType scalarType = typeManager.getScalarType(propType, property.getDbType()); + if (scalarType != null) { + return scalarType; + } + + String msg = property.getFullBeanName()+" has no ScalarType - type[" + propType.getName() + "]"; + if (!property.isTransient()){ + throw new PersistenceException(msg); + + } else { + // this is ok... + logger.finest("... transient property "+msg); + return null; + } + } + + /** + * This property is marked as a Lob object. + */ + public void setLobType(DeployBeanProperty prop) { + + // is String or byte[] ? used to determine if its a CLOB or BLOB + Class type = prop.getPropertyType(); + + // this also sets the lob flag on DeployBeanProperty + int lobType = isClobType(type) ? dbCLOBType : dbBLOBType; + + ScalarType scalarType = typeManager.getScalarType(type, lobType); + if (scalarType == null) { + // this should never occur actually + throw new RuntimeException("No ScalarType for LOB type ["+type+"] ["+lobType+"]"); + } + prop.setDbType(lobType); + prop.setScalarType(scalarType); + } + + public boolean isClobType(Class type){ + if (type.equals(String.class)){ + return true; + } + return false; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DetectScala.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DetectScala.java new file mode 100644 index 000000000..35e106924 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DetectScala.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.api.ClassUtil; + +/** + * Used to detected if Scala support is required. + * + * @author rbygrave + */ +public class DetectScala { + + private static final Logger logger = Logger.getLogger(DetectScala.class.getName()); + + private static Class scalaOptionClass = initScalaOptionClass(); + + private static boolean hasScalaSupport = scalaOptionClass != null; + + private static Class initScalaOptionClass() { + try { + return ClassUtil.forName("scala.Option"); + } catch (ClassNotFoundException e) { + // scala not in the classpath... + logger.fine("Scala type 'scala.Option' not found. Scala Support disabled."); + return null; + } + } + + /** + * Return true if scala is in the classpath. + */ + public static boolean hasScalaSupport() { + return hasScalaSupport; + } + + /** + * Return the scala.Option class or null if scala is not in the classpath. + */ + public static Class getScalaOptionClass() { + return scalaOptionClass; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java new file mode 100644 index 000000000..35e62c3bb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; + + +/** + * Read the deployment annotations for the bean. + */ +public class ReadAnnotations { + + /** + * Read the initial non-relationship annotations included Id and EmbeddedId. + *

+ * We then have enough to create BeanTables which are used in readAssociations + * to resolve the relationships etc. + *

+ */ + public void readInitial(DeployBeanInfo info){ + + try { + new AnnotationClass(info).parse(); + new AnnotationFields(info).parse(); + + } catch (RuntimeException e){ + String msg = "Error reading annotations for "+info; + throw new RuntimeException(msg, e); + } + } + + + /** + * Read and process the associated relationship annotations. + *

+ * These can only be processed after the BeanTables have been created + *

+ *

+ * This uses the factory as a call back to get the BeanTable for a given + * associated bean. + *

+ */ + public void readAssociations(DeployBeanInfo info, BeanDescriptorManager factory){ + + try { + + new AnnotationAssocOnes(info, factory).parse(); + new AnnotationAssocManys(info, factory).parse(); + + // read the Sql annotations last because they may be + // dependent on field level annotations + new AnnotationSql(info).parse(); + + } catch (RuntimeException e){ + String msg = "Error reading annotations for "+info; + throw new RuntimeException(msg, e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/SqlReservedWords.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/SqlReservedWords.java new file mode 100644 index 000000000..eeec13ac3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/SqlReservedWords.java @@ -0,0 +1,78 @@ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.util.HashSet; + +/** + * Keywords deemed inappropriate for use as table alias' column or table name. + *

+ * That is, they might cause sql syntax errors if used. + *

+ */ +public class SqlReservedWords { + + private static final String baseKeyWords = + "ALIAS,ALTER,ADD,ALL,ARE,AND,ANY,ARRAY" + +",AS,ASC,AT" + +",AVG,BEGIN,BETWEEN,BIGINT,BINARY,BIT,BIT_LENGTH,BLOB,BOOLEAN" + +",BOTH,BY,CALL,CALLED,CASCADE,CASCADED,CASE,CAST,CATALOG,CHAR" + +",CHARACTER,CHECK,CLOB,CLOSE,COALESCE,COLLATE" + +",COLLATION,COLUMN,COMMIT,CONDITION,CONNECT,CONNECTION,CONSTRAINT" + +",CONSTRAINTS,CONSTRUCTOR,CONTAINS,CONTINUE,CONVERT" + +",COUNT,CREATE,CROSS,CURRENT_DATE" + +",CURRENT_PATH,CURRENT_ROLE,CURRENT_TIME,CURRENT_TIMESTAMP" + +",CURRENT_USER,CURSOR,DATE" + +",DAY,DEC,DECIMAL,DECLARE,DEFAULT,DELETE" + +",DESC" + +",DISTINCT,DO,DOUBLE,DROP,ELSE,ELSEIF,END,EQUALS" + +",EXEC,EXIT,EXISTS,EXTRACT" + +",FLOAT,FROM,FOR,FREE" + +",GET,GLOBAL,GO,GOTO,GRANT,GROUP,HAVING,HOUR" + +",IF,IN,INNER,INOUT,INSERT" + +",INT,INTEGER,INTO,IS,JOIN" + +",LAST,LIKE,LIMIT" + +",MAX,MIN" + +",NCHAR,NCLOB,NOT" + +",NULL,NULLIF,NUMERIC," + +",OR,ORDER,OUTER" + +",REAL,REF,REFERENCES" + +",RETURN,RETURNS" + +",SELECT,SSL" + +",SMALLINT," + +",SYSTEM,SYSTEM_USER,TABLE" + +",TO" + +",TRIGGER,UNION,UNIQUE" + +",UPDATE,USER,VARCHAR,VIEW,WHEN" + +",WHERE,WITH"; + + + private static HashSet keywords = new HashSet(); + static { + + String[] initialKeywords = baseKeyWords.split(","); + for (int i = 0; i < initialKeywords.length; i++) { + keywords.add(initialKeywords[i].trim()); + } + + } + + + /** + * Return true if the tableAlias is a keyword. + */ + public static synchronized boolean isKeyword(String keyword){ + String s = keyword.trim().toUpperCase(); + return keywords.contains(s); + } + + /** + * Add a sql keyword to the known set. + */ + public static synchronized void addKeyword(String keyword){ + if (keyword != null){ + keyword = keyword.trim().toUpperCase(); + if (keyword.length() > 0){ + keywords.add(keyword); + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/TransientProperties.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/TransientProperties.java new file mode 100644 index 000000000..e4b3c76d2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/TransientProperties.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; + +/** + * Mark transient properties. + */ +public class TransientProperties { + + public TransientProperties() { + } + + /** + * Mark any additional properties as transient. + */ + public void process(DeployBeanDescriptor desc) { + + List props = desc.propertiesBase(); + for (int i = 0; i < props.size(); i++) { + DeployBeanProperty prop = props.get(i); + if (!prop.isDbRead() && !prop.isDbInsertable() && !prop.isDbUpdateable()) { + // non-transient... + prop.setTransient(true); + } + } + + List> ones = desc.propertiesAssocOne(); + for (int i = 0; i < ones.size(); i++) { + DeployBeanPropertyAssocOne prop = ones.get(i); + if (prop.getBeanTable() == null) { + if (!prop.isEmbedded()) { + prop.setTransient(true); + } + } + } + + List> manys = desc.propertiesAssocMany(); + for (int i = 0; i < manys.size(); i++) { + DeployBeanPropertyAssocMany prop = manys.get(i); + if (prop.getBeanTable() == null) { + prop.setTransient(true); + } + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ValidatorFactoryManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ValidatorFactoryManager.java new file mode 100644 index 000000000..08dfdfab5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ValidatorFactoryManager.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.deploy.parse; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.validation.ValidatorMeta; +import com.avaje.ebean.validation.factory.Validator; +import com.avaje.ebean.validation.factory.ValidatorFactory; + +public class ValidatorFactoryManager { + + static final Logger logger = Logger.getLogger(ValidatorFactoryManager.class.getName()); + + Map, ValidatorFactory> factoryMap; + + public ValidatorFactoryManager() { + factoryMap = new HashMap, ValidatorFactory>(); + } + + public Validator create(Annotation ann, Class type) { + synchronized (this) { + ValidatorMeta meta = ann.annotationType().getAnnotation(ValidatorMeta.class); + if (meta == null) { + return null; + } + Class factoryClass = meta.factory(); + ValidatorFactory factory = getFactory(factoryClass); + return factory.create(ann, type); + } + } + + private ValidatorFactory getFactory(Class factoryClass) { + try { + ValidatorFactory factory = factoryMap.get(factoryClass); + if (factory == null) { + factory = (ValidatorFactory) factoryClass.newInstance(); + factoryMap.put(factoryClass, factory); + } + return factory; + + } catch (Exception e) { + String msg = "Error creating ValidatorFactory " + factoryClass.getName(); + logger.log(Level.SEVERE, msg, e); + return null; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ebeandeploy.dtd b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ebeandeploy.dtd new file mode 100644 index 000000000..37a869e4e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ebeandeploy.dtd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/package.html b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/package.html new file mode 100644 index 000000000..b4c4bceec --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/package.html @@ -0,0 +1,11 @@ + + + + Deployment information parsing + + +Deployment information parsing + + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/CharMatch.java b/src/main/java/com/avaje/ebeaninternal/server/el/CharMatch.java new file mode 100644 index 000000000..932ac0db0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/CharMatch.java @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +/** + * Case insensitive string matching. + *

+ * Provides an alternative to using regular expressions. + *

+ */ +public final class CharMatch { + + private final char[] upperChars; + + private final int maxLength; + + public CharMatch(String s) { + this.upperChars = s.toUpperCase().toCharArray(); + this.maxLength = upperChars.length; + } + + public boolean startsWith(String other) { + + if (other == null || other.length() < maxLength){ + return false; + } + + char ta[] = other.toCharArray(); + + int pos = -1; + while (++pos < maxLength) { + char c1 = upperChars[pos]; + char c2 = Character.toUpperCase(ta[pos]); + if (c1 != c2) { + return false; + } + } + return true; + } + + public boolean endsWith(String other) { + + if (other == null || other.length() < maxLength){ + return false; + } + + char ta[] = other.toCharArray(); + + int offset = ta.length - maxLength; + int pos = maxLength; + while (--pos >= 0) { + char c1 = upperChars[pos]; + char c2 = Character.toUpperCase(ta[offset+pos]); + if (c1 != c2) { + return false; + } + } + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElComparator.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElComparator.java new file mode 100644 index 000000000..8dc93af91 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElComparator.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import java.util.Comparator; + +/** + * Comparator for use with the expression objects. + */ +public interface ElComparator extends Comparator { + + /** + * Compare given 2 beans. + */ + public int compare(T o1, T o2); + + /** + * Compare with a fixed value to a given bean. + */ + public int compareValue(Object value, T o2); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorCompound.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorCompound.java new file mode 100644 index 000000000..57e29b428 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorCompound.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import java.util.Comparator; + +/** + * Comparator based on multiple ordered comparators. + *

+ * eg. "name, orderDate desc, id" + *

+ */ +public final class ElComparatorCompound implements Comparator, ElComparator { + + private final ElComparator[] array; + + public ElComparatorCompound(ElComparator[] array) { + this.array = array; + } + + public int compare(T o1, T o2) { + + for (int i = 0; i < array.length; i++) { + int ret = array[i].compare(o1, o2); + if (ret != 0){ + return ret; + } + } + + return 0; + } + + public int compareValue(Object value, T o2) { + + for (int i = 0; i < array.length; i++) { + int ret = array[i].compareValue(value, o2); + if (ret != 0){ + return ret; + } + } + + return 0; + } + + + + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorProperty.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorProperty.java new file mode 100644 index 000000000..99d1efd2b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElComparatorProperty.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import java.util.Comparator; + +/** + * Comparator based on a ElGetValue. + */ +public final class ElComparatorProperty implements Comparator, ElComparator { + + private final ElPropertyValue elGetValue; + + private final int nullOrder; + + private final int asc; + + public ElComparatorProperty(ElPropertyValue elGetValue, boolean ascending, boolean nullsHigh) { + this.elGetValue = elGetValue; + this.asc = ascending ? 1 : -1; + this.nullOrder = asc * (nullsHigh ? 1 : -1); + } + + public int compare(T o1, T o2) { + + Object val1 = elGetValue.elGetValue(o1); + Object val2 = elGetValue.elGetValue(o2); + + return compareValues(val1, val2); + } + + public int compareValue(Object value, T o2) { + + Object val2 = elGetValue.elGetValue(o2); + + return compareValues(value, val2); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public int compareValues(Object val1, Object val2){ + + if (val1 == null){ + return val2 == null ? 0 : nullOrder; + } + if (val2 == null){ + return -1 * nullOrder; + } + Comparable c = (Comparable)val1; + return asc * c.compareTo(val2); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElFilter.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElFilter.java new file mode 100644 index 000000000..9cec00687 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElFilter.java @@ -0,0 +1,271 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import com.avaje.ebean.Filter; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Default implementation of the Filter interface. + */ +public final class ElFilter implements Filter { + + private final BeanDescriptor beanDescriptor; + + private ArrayList> matches = new ArrayList>(); + + private int maxRows; + + private String sortByClause; + + public ElFilter(BeanDescriptor beanDescriptor) { + this.beanDescriptor = beanDescriptor; + } + + private Object convertValue(String propertyName, Object value) { + // convert type of value to match expected type + ElPropertyValue elGetValue = beanDescriptor.getElGetValue(propertyName); + return elGetValue.elConvertType(value); + } + + private ElComparator getElComparator(String propertyName) { + + return beanDescriptor.getElComparator(propertyName); + } + + private ElPropertyValue getElGetValue(String propertyName) { + + return beanDescriptor.getElGetValue(propertyName); + } + + public Filter sort(String sortByClause) { + this.sortByClause = sortByClause; + return this; + } + + protected boolean isMatch(T bean) { + for (int i = 0; i < matches.size(); i++) { + ElMatcher matcher = matches.get(i); + if (!matcher.isMatch(bean)){ + return false; + } + } + return true; + } + + + public Filter in(String propertyName, Set matchingValues) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + + matches.add(new ElMatchBuilder.InSet(matchingValues, elGetValue)); + return this; + } + + public Filter eq(String propertyName, Object value) { + + value = convertValue(propertyName, value); + ElComparator comparator = getElComparator(propertyName); + + matches.add(new ElMatchBuilder.Eq(value, comparator)); + return this; + } + + + public Filter ne(String propertyName, Object value) { + + value = convertValue(propertyName, value); + ElComparator comparator = getElComparator(propertyName); + + matches.add(new ElMatchBuilder.Ne(value, comparator)); + return this; + } + + public Filter between(String propertyName, Object min, Object max) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + min = elGetValue.elConvertType(min); + max = elGetValue.elConvertType(max); + + ElComparator elComparator = getElComparator(propertyName); + + matches.add(new ElMatchBuilder.Between(min, max, elComparator)); + return this; + } + + + public Filter gt(String propertyName, Object value) { + + value = convertValue(propertyName, value); + ElComparator comparator = getElComparator(propertyName); + + matches.add(new ElMatchBuilder.Gt(value, comparator)); + return this; + } + + public Filter ge(String propertyName, Object value) { + + value = convertValue(propertyName, value); + ElComparator comparator = getElComparator(propertyName); + + matches.add(new ElMatchBuilder.Ge(value, comparator)); + return this; + } + + public Filter ieq(String propertyName, String value) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + + matches.add(new ElMatchBuilder.Ieq(elGetValue, value)); + return this; + } + + + public Filter isNotNull(String propertyName) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + + matches.add(new ElMatchBuilder.IsNotNull(elGetValue)); + return this; + } + + + public Filter isNull(String propertyName) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + + matches.add(new ElMatchBuilder.IsNull(elGetValue)); + return this; + } + + + public Filter le(String propertyName, Object value) { + + value = convertValue(propertyName, value); + ElComparator comparator = getElComparator(propertyName); + + matches.add(new ElMatchBuilder.Le(value, comparator)); + return this; + } + + + public Filter lt(String propertyName, Object value) { + + value = convertValue(propertyName, value); + ElComparator comparator = getElComparator(propertyName); + + matches.add(new ElMatchBuilder.Lt(value, comparator)); + return this; + } + + + public Filter regex(String propertyName, String regEx) { + return regex(propertyName, regEx, 0); + } + + public Filter regex(String propertyName, String regEx, int options) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + + matches.add(new ElMatchBuilder.RegularExpr(elGetValue, regEx, options)); + return this; + } + + public Filter contains(String propertyName, String value) { + + String quote = ".*"+Pattern.quote(value)+".*"; + + ElPropertyValue elGetValue = getElGetValue(propertyName); + matches.add(new ElMatchBuilder.RegularExpr(elGetValue, quote, 0)); + return this; + } + + public Filter icontains(String propertyName, String value) { + + String quote = ".*"+Pattern.quote(value)+".*"; + + ElPropertyValue elGetValue = getElGetValue(propertyName); + matches.add(new ElMatchBuilder.RegularExpr(elGetValue, quote, Pattern.CASE_INSENSITIVE)); + return this; + } + + + public Filter endsWith(String propertyName, String value) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + matches.add(new ElMatchBuilder.EndsWith(elGetValue, value)); + return this; + } + + public Filter startsWith(String propertyName, String value) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + matches.add(new ElMatchBuilder.StartsWith(elGetValue, value)); + return this; + } + + public Filter iendsWith(String propertyName, String value) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + matches.add(new ElMatchBuilder.IEndsWith(elGetValue, value)); + return this; + } + + public Filter istartsWith(String propertyName, String value) { + + ElPropertyValue elGetValue = getElGetValue(propertyName); + matches.add(new ElMatchBuilder.IStartsWith(elGetValue, value)); + return this; + } + + public Filter maxRows(int maxRows) { + this.maxRows = maxRows; + return this; + } + + public List filter(List list) { + + if (sortByClause != null){ + // create shallow copy and sort + list = new ArrayList(list); + beanDescriptor.sort(list, sortByClause); + } + + ArrayList filterList = new ArrayList(); + + for (int i = 0; i < list.size(); i++) { + T t = list.get(i); + if (isMatch(t)) { + filterList.add(t); + if (maxRows > 0 && filterList.size() >= maxRows){ + break; + } + } + } + + return filterList; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElMatchBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElMatchBuilder.java new file mode 100644 index 000000000..49c0e6b93 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElMatchBuilder.java @@ -0,0 +1,305 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + + +/** + * Contains the various ElMatcher implementations. + */ +class ElMatchBuilder { + + /** + * Case insensitive equals. + */ + static class RegularExpr implements ElMatcher { + + final ElPropertyValue elGetValue; + final String value; + final Pattern pattern; + + RegularExpr(ElPropertyValue elGetValue, String value, int options){ + this.elGetValue = elGetValue; + this.value = value; + this.pattern = Pattern.compile(value, options); + } + + public boolean isMatch(T bean) { + String v = (String)elGetValue.elGetValue(bean); + return pattern.matcher(v).matches(); + } + } + + /** + * Case insensitive equals. + */ + static abstract class BaseString implements ElMatcher { + + final ElPropertyValue elGetValue; + final String value; + + public BaseString(ElPropertyValue elGetValue, String value){ + this.elGetValue = elGetValue; + this.value = value; + } + + public abstract boolean isMatch(T bean); + } + + static class Ieq extends BaseString { + Ieq(ElPropertyValue elGetValue, String value) { + super(elGetValue, value); + } + + public boolean isMatch(T bean) { + String v = (String)elGetValue.elGetValue(bean); + return value.equalsIgnoreCase(v); + } + } + + /** + * Case insensitive starts with matcher. + */ + static class IStartsWith implements ElMatcher { + + final ElPropertyValue elGetValue; + final CharMatch charMatch; + + IStartsWith(ElPropertyValue elGetValue, String value) { + this.elGetValue = elGetValue; + this.charMatch = new CharMatch(value); + } + + public boolean isMatch(T bean) { + + String v = (String)elGetValue.elGetValue(bean); + return charMatch.startsWith(v); + } + } + + /** + * Case insensitive ends with matcher. + */ + static class IEndsWith implements ElMatcher { + + final ElPropertyValue elGetValue; + final CharMatch charMatch; + + IEndsWith(ElPropertyValue elGetValue, String value) { + this.elGetValue = elGetValue; + this.charMatch = new CharMatch(value); + } + + public boolean isMatch(T bean) { + + String v = (String)elGetValue.elGetValue(bean); + return charMatch.endsWith(v); + } + } + + static class StartsWith extends BaseString { + StartsWith(ElPropertyValue elGetValue, String value) { + super(elGetValue, value); + } + + public boolean isMatch(T bean) { + String v = (String)elGetValue.elGetValue(bean); + return value.startsWith(v); + } + } + + static class EndsWith extends BaseString { + EndsWith(ElPropertyValue elGetValue, String value) { + super(elGetValue, value); + } + + public boolean isMatch(T bean) { + String v = (String)elGetValue.elGetValue(bean); + return value.endsWith(v); + } + } + + static class IsNull implements ElMatcher { + + final ElPropertyValue elGetValue; + + public IsNull(ElPropertyValue elGetValue){ + this.elGetValue = elGetValue; + } + + public boolean isMatch(T bean) { + return (null == elGetValue.elGetValue(bean)); + } + } + + static class IsNotNull implements ElMatcher { + + final ElPropertyValue elGetValue; + + public IsNotNull(ElPropertyValue elGetValue){ + this.elGetValue = elGetValue; + } + + public boolean isMatch(T bean) { + return (null != elGetValue.elGetValue(bean)); + } + } + + static abstract class Base implements ElMatcher { + + final Object filterValue; + + final ElComparator comparator; + + public Base(Object filterValue, ElComparator comparator){ + this.filterValue = filterValue; + this.comparator = comparator; + } + + public abstract boolean isMatch(T value); + } + + static class InSet implements ElMatcher { + + final Set set; + final ElPropertyValue elGetValue; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public InSet(Set set, ElPropertyValue elGetValue){ + this.set = new HashSet(set); + this.elGetValue = elGetValue; + } + + public boolean isMatch(T bean) { + + Object value = elGetValue.elGetValue(bean); + if (value == null){ + return false; + } + + return set.contains(value); + } + + } + /** + * Equal To. + */ + static class Eq extends Base { + + public Eq(Object filterValue, ElComparator comparator){ + super(filterValue, comparator); + } + + public boolean isMatch(T value) { + return comparator.compareValue(filterValue, value) == 0; + } + } + + /** + * Not Equal To. + */ + static class Ne extends Base { + + public Ne(Object filterValue, ElComparator comparator){ + super(filterValue, comparator); + } + + public boolean isMatch(T value) { + return comparator.compareValue(filterValue, value) != 0; + } + } + + /** + * Between. + */ + static class Between implements ElMatcher { + + final Object min; + final Object max; + final ElComparator comparator; + + Between(Object min, Object max, ElComparator comparator){ + this.min = min; + this.max = max; + this.comparator = comparator; + } + + public boolean isMatch(T value) { + + return (comparator.compareValue(min, value) <= 0 + && comparator.compareValue(max, value) >= 0); + } + } + + /** + * Greater Than. + */ + static class Gt extends Base { + Gt(Object filterValue, ElComparator comparator){ + super(filterValue, comparator); + } + + public boolean isMatch(T value) { + return comparator.compareValue(filterValue, value) == -1; + } + } + + /** + * Greater Than or Equal To. + */ + static class Ge extends Base { + Ge(Object filterValue, ElComparator comparator){ + super(filterValue, comparator); + } + + public boolean isMatch(T value) { + return comparator.compareValue(filterValue, value) >= 0; + } + } + + /** + * Less Than or Equal To. + */ + static class Le extends Base { + Le(Object filterValue, ElComparator comparator){ + super(filterValue, comparator); + } + + public boolean isMatch(T value) { + return comparator.compareValue(filterValue, value) <= 0; + } + } + + /** + * Less Than. + */ + static class Lt extends Base { + Lt(Object filterValue, ElComparator comparator){ + super(filterValue, comparator); + } + + public boolean isMatch(T value) { + return comparator.compareValue(filterValue, value) == 1; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElMatcher.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElMatcher.java new file mode 100644 index 000000000..228a78d76 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElMatcher.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +/** + * Interface for defining matches for filter expressions. + */ +public interface ElMatcher { + + /** + * Return true if the bean matches the expression. + */ + public boolean isMatch(T bean); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java new file mode 100644 index 000000000..25f0cdefc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java @@ -0,0 +1,331 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.text.StringFormatter; +import com.avaje.ebean.text.StringParser; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.avaje.ebeaninternal.server.type.ScalarType; + + +/** + * A ElGetValue based on a chain of properties. + *

+ * Used to get the value for an compound expression like customer.name or + * customer.shippingAddress.city etc. + *

+ *

+ * Note that if any element in the chain returns null, then null is returned and + * no further processing of the chain occurs. + *

+ */ +public class ElPropertyChain implements ElPropertyValue { + + private final String prefix; + + private final String placeHolder; + private final String placeHolderEncrypted; + + private final String name; + + private final String expression; + + private final boolean containsMany; + + private final ElPropertyValue[] chain; + + private final boolean assocId; + private final int last; + private final BeanProperty lastBeanProperty; + private final ScalarType scalarType; + + private final ElPropertyValue lastElPropertyValue; + + public ElPropertyChain(boolean containsMany, boolean embedded, String expression, ElPropertyValue[] chain) { + + this.containsMany = containsMany; + this.chain = chain; + this.expression = expression; + int dotPos = expression.lastIndexOf('.'); + if (dotPos > -1){ + this.name = expression.substring(dotPos+1); + if (embedded){ + int embPos = expression.lastIndexOf('.',dotPos-1); + this.prefix = embPos == -1 ? null : expression.substring(0, embPos); + + } else { + this.prefix = expression.substring(0, dotPos); + } + } else { + this.prefix = null; + this.name = expression; + } + + this.assocId = chain[chain.length-1].isAssocId(); + + this.last = chain.length-1; + this.lastBeanProperty = chain[chain.length-1].getBeanProperty(); + if (lastBeanProperty != null){ + this.scalarType = lastBeanProperty.getScalarType(); + } else { + // case for nested compound type (non-scalar) + this.scalarType = null; + } + this.lastElPropertyValue = chain[chain.length-1]; + this.placeHolder = getElPlaceHolder(prefix, lastElPropertyValue, false); + this.placeHolderEncrypted = getElPlaceHolder(prefix, lastElPropertyValue, true); + } + + private String getElPlaceHolder(String prefix, ElPropertyValue lastElPropertyValue, boolean encrypted) { + if (prefix == null){ + return lastElPropertyValue.getElPlaceholder(encrypted); + } + + String el = lastElPropertyValue.getElPlaceholder(encrypted); + + if (!el.contains("${}")){ + // typically a secondary table property + return StringHelper.replaceString(el, "${", "${"+prefix+"."); + } else { + return StringHelper.replaceString(el, ROOT_ELPREFIX, "${"+prefix+"}"); + } + } + + /** + * Full ElGetValue support. + */ + public boolean isDeployOnly() { + return false; + } + + /** + * Return true if there is a many property from sinceProperty to + * the end of this chain. + */ + public boolean containsManySince(String sinceProperty) { + if (sinceProperty == null){ + return containsMany; + } + if (!expression.startsWith(sinceProperty)){ + return containsMany; + } + + int i = 1 + SplitName.count('.', sinceProperty); + + for (; i < chain.length; i++) { + if (chain[i].getBeanProperty().containsMany()) { + return true; + } + } + + return false; + } + + public boolean containsMany() { + return containsMany; + } + + public String getElPrefix() { + return prefix; + } + + public String getName() { + return name; + } + + public String getElName() { + return expression; + } + + public String getElPlaceholder(boolean encrypted) { + return encrypted ? placeHolderEncrypted : placeHolder; + } + + public boolean isDbEncrypted() { + return lastElPropertyValue.isDbEncrypted(); + } + + public boolean isLocalEncrypted() { + return lastElPropertyValue.isLocalEncrypted(); + } + + public Object[] getAssocOneIdValues(Object bean) { + // Don't navigate the object graph as bean + // is assumed to be the appropriate type + return lastElPropertyValue.getAssocOneIdValues(bean); + } + + public String getAssocOneIdExpr(String prefix, String operator) { + return lastElPropertyValue.getAssocOneIdExpr(expression, operator); + } + + public String getAssocIdInExpr(String prefix) { + return lastElPropertyValue.getAssocIdInExpr(prefix); + } + + public String getAssocIdInValueExpr(int size) { + return lastElPropertyValue.getAssocIdInValueExpr(size); + } + + public int getDeployOrder() { + int i = lastBeanProperty.getDeployOrder(); + int max = chain.length-1; + for (int j = 0; j < max; j++) { + int xtra = ((max-j)*1000) * chain[j].getDeployOrder(); + i += xtra; + } + return i; + } + + public boolean isAssocId() { + return assocId; + } + + public boolean isAssocProperty() { + for (int i = 0; i < chain.length; i++) { + if (chain[i].isAssocProperty()){ + return true; + } + } + return false; + } + + public String getDbColumn() { + return lastElPropertyValue.getDbColumn(); + } + + public BeanProperty getBeanProperty() { + return lastBeanProperty; + } + + + public boolean isDateTimeCapable() { + return scalarType != null && scalarType.isDateTimeCapable(); + } + + public int getJdbcType() { + return scalarType == null ? 0 : scalarType.getJdbcType(); + } + + public Object parseDateTime(long systemTimeMillis) { + return scalarType.parseDateTime(systemTimeMillis); + } + + public StringParser getStringParser() { + return scalarType; + } + + public StringFormatter getStringFormatter() { + return scalarType; + } + + public Object elConvertType(Object value){ + // just convert using the last one in the chain + return lastElPropertyValue.elConvertType(value); + } + + public Object elGetValue(Object bean) { + + for (int i = 0; i < chain.length; i++) { + bean = chain[i].elGetValue(bean); + if (bean == null) { + return null; + } + } + + return bean; + } + + public Object elGetReference(Object bean) { + + Object prevBean = bean; + for (int i = 0; i < last; i++) { + // always return non null prevBean + prevBean = chain[i].elGetReference(prevBean); + } + // try the last step in the chain + bean = chain[last].elGetValue(prevBean); + + return bean; + } + + + public void elSetLoaded(Object bean) { + + for (int i = 0; i < last; i++) { + bean = chain[i].elGetValue(bean); + if (bean == null){ + break; + } + } + if (bean != null){ + ((EntityBean)bean)._ebean_getIntercept().setLoaded(); + } + } + + public void elSetReference(Object bean) { + + for (int i = 0; i < last; i++) { + bean = chain[i].elGetValue(bean); + if (bean == null){ + break; + } + } + if (bean != null){ + ((EntityBean)bean)._ebean_getIntercept().setReference(); + } + } + + public void elSetValue(Object bean, Object value, boolean populate, boolean reference){ + + Object prevBean = bean; + if (populate){ + for (int i = 0; i < last; i++) { + // always return non null prevBean + prevBean = chain[i].elGetReference(prevBean); + } + } else { + for (int i = 0; i < last; i++) { + // always return non null prevBean + prevBean = chain[i].elGetValue(prevBean); + if (prevBean == null){ + break; + } + } + } + if (prevBean != null){ + if (lastBeanProperty != null){ + // last chain element maps to a real scalar property + lastBeanProperty.setValueIntercept(prevBean, value); + if (reference){ + ((EntityBean)prevBean)._ebean_getIntercept().setReference(); + } + } else { + // a non-scalar property of a Compound value object + lastElPropertyValue.elSetValue(prevBean, value, populate, reference); + } + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChainBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChainBuilder.java new file mode 100644 index 000000000..72b453fdc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChainBuilder.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility object used to build a ElPropertyChain. + *

+ * Builds a ElPropertyChain based on a chain of properties with dot separators. + *

+ *

+ * This can navigate an object graph based on dot notation such as + * order.customer.name. + *

+ */ +public class ElPropertyChainBuilder { + + private final String expression; + + private final List chain = new ArrayList(); + + private final boolean embedded; + + private boolean containsMany; + + /** + * Create with the original expression. + */ + public ElPropertyChainBuilder(boolean embedded, String expression) { + this.embedded = embedded; + this.expression = expression; + } + + public boolean isContainsMany() { + return containsMany; + } + + public void setContainsMany(boolean containsMany) { + this.containsMany = containsMany; + } + + public String getExpression() { + return expression; + } + + /** + * Add a ElGetValue element to the chain. + */ + public ElPropertyChainBuilder add(ElPropertyValue element) { + if (element == null){ + throw new NullPointerException("element null in expression "+expression); + } + chain.add(element); + return this; + } + + /** + * Build the immutable ElGetChain from the build information. + */ + public ElPropertyChain build() { + return new ElPropertyChain(containsMany, embedded, expression, chain.toArray(new ElPropertyValue[chain.size()])); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyDeploy.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyDeploy.java new file mode 100644 index 000000000..a544612bb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyDeploy.java @@ -0,0 +1,68 @@ +package com.avaje.ebeaninternal.server.el; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Used to parse expressions in queries (where, orderBy etc). + *

+ * Maps an expression to deployment information such as + * the DB column and elPrefix/elPlaceHolder is used determine + * joins and set place holders for table alias'. + *

+ */ +public interface ElPropertyDeploy { + + /** + * This is the elPrefix for all root level properties. + */ + public static final String ROOT_ELPREFIX = "${}"; + + /** + * Return true if there is a property on the path that is a many property. + */ + public boolean containsMany(); + + /** + * Return true if there is a property is on the path after sinceProperty + * that is a 'many' property. + */ + public boolean containsManySince(String sinceProperty); + + /** + * Return the prefix path of the property. + *

+ * This is use to determine joins required to support + * this property. + *

+ */ + public String getElPrefix(); + + /** + * Return the place holder in the form of ${elPrefix}dbColumn. + *

+ * The ${elPrefix} is replaced by the appropriate table alias. + *

+ */ + public String getElPlaceholder(boolean encrypted); + + /** + * Return the name of the property. + */ + public String getName(); + + /** + * The ElPrefix plus name. + */ + public String getElName(); + + /** + * Return the deployment db column for this property. + */ + public String getDbColumn(); + + + /** + * Return the underlying bean property. + */ + public BeanProperty getBeanProperty(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java new file mode 100644 index 000000000..9659b142b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java @@ -0,0 +1,140 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +import com.avaje.ebean.text.StringFormatter; +import com.avaje.ebean.text.StringParser; + +/** + * The expression language object that can get values. + *

+ * This can be used for local sorting and filtering. + *

+ */ +public interface ElPropertyValue extends ElPropertyDeploy { + + /** + * Return the Id values for the given bean value. + */ + public Object[] getAssocOneIdValues(Object bean); + + /** + * Return the Id expression string. + *

+ * Typically used to produce id = ? expression strings. + *

+ */ + public String getAssocOneIdExpr(String prefix, String operator); + + /** + * Return the logical id value expression taking into account embedded id's. + */ + public String getAssocIdInValueExpr(int size); + + /** + * Return the logical id in expression taking into account embedded id's. + */ + public String getAssocIdInExpr(String prefix); + + /** + * Return true if this is an ManyToOne or OneToOne associated bean property. + */ + public boolean isAssocId(); + + /** + * Return true if any path of this path contains a Associated One or Many. + */ + public boolean isAssocProperty(); + + /** + * Return true if the property is encrypted via Java. + */ + public boolean isLocalEncrypted(); + + /** + * Return true if the property is encrypted in the DB. + */ + public boolean isDbEncrypted(); + + /** + * Return the deploy order for the property. + */ + public int getDeployOrder(); + + /** + * Return the default StringParser for the scalar property. + */ + public StringParser getStringParser(); + + /** + * Return the default StringFormatter for the scalar property. + */ + public StringFormatter getStringFormatter(); + + /** + * Return true if the last type is "DateTime capable" - can support + * {@link #parseDateTime(long)}. + */ + public boolean isDateTimeCapable(); + + /** + * Return the underlying JDBC type or 0 if this is not a scalar type. + */ + public int getJdbcType(); + + /** + * For DateTime capable scalar types convert the long systemTimeMillis into + * an appropriate java time (Date,Timestamp,Time,Calendar, JODA type etc). + */ + public Object parseDateTime(long systemTimeMillis); + + /** + * Return the value from a given entity bean. + */ + public Object elGetValue(Object bean); + + /** + * Return the value ensuring objects prior to the top scalar property are + * automatically populated. + */ + public Object elGetReference(Object bean); + + /** + * Set a value given a root level bean. + *

+ * If populate then + *

+ */ + public void elSetValue(Object bean, Object value, boolean populate, boolean reference); + + /** + * Make the owning bean of this property a reference (as in not new/dirty). + */ + public void elSetReference(Object bean); + + /** + * Convert the value to the expected type. + *

+ * Typically useful for converting strings to the appropriate number type + * etc. + *

+ */ + public Object elConvertType(Object value); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElSetValue.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElSetValue.java new file mode 100644 index 000000000..fcee1c9c7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElSetValue.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.el; + +public interface ElSetValue { + + /** + * Set the value to the bean. + */ + public void elSetValue(Object bean, Object value); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/el/package-info.java new file mode 100644 index 000000000..22a02b845 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/el/package-info.java @@ -0,0 +1,4 @@ +/** + * Expression language. + */ +package com.avaje.ebeaninternal.server.el; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java new file mode 100644 index 000000000..01b3ebbb7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AbstractExpression.java @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Base class for simple expressions. + * + * @author rbygrave + */ +public abstract class AbstractExpression implements SpiExpression { + + private static final long serialVersionUID = 4072786211853856174L; + + protected final String propName; + + protected final FilterExprPath pathPrefix; + + protected AbstractExpression(FilterExprPath pathPrefix, String propName) { + this.pathPrefix = pathPrefix; + this.propName = propName; + } + + public String getPropertyName() { + if (pathPrefix == null){ + return propName; + } else { + String path = pathPrefix.getPath(); + if (path == null || path.length() == 0){ + return propName; + } else { + return path+"."+propName; + } + } + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + String propertyName = getPropertyName(); + if (propertyName != null){ + ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName); + if (elProp != null && elProp.containsMany()){ + manyWhereJoin.add(elProp); + } + } + } + + protected ElPropertyValue getElProp(SpiExpressionRequest request) { + + String propertyName = getPropertyName(); + return request.getBeanDescriptor().getElGetValue(propertyName); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java new file mode 100644 index 000000000..c82a66abf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java @@ -0,0 +1,133 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; + +class AllEqualsExpression implements SpiExpression { + + private static final long serialVersionUID = -8691773558205937025L; + + private final Map propMap; + + private final FilterExprPath pathPrefix; + + AllEqualsExpression(FilterExprPath pathPrefix, Map propMap) { + this.pathPrefix = pathPrefix; + this.propMap = propMap; + } + + protected String name(String propName) { + if (pathPrefix == null) { + return propName; + } else { + String path = pathPrefix.getPath(); + if (path == null || path.length() == 0) { + return propName; + } else { + return path + "." + propName; + } + } + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + if (propMap != null) { + Iterator it = propMap.keySet().iterator(); + while (it.hasNext()) { + String propertyName = it.next(); + ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(propertyName)); + if (elProp != null && elProp.containsMany()) { + manyWhereJoin.add(elProp); + } + } + } + } + + public void addBindValues(SpiExpressionRequest request) { + + if (propMap.isEmpty()) { + return; + } + Iterator it = propMap.values().iterator(); + while (it.hasNext()) { + Object value = it.next(); + if (value != null) { + request.addBindValue(value); + } else { + // null value uses is null clause + } + } + } + + public void addSql(SpiExpressionRequest request) { + + if (propMap.isEmpty()) { + return; + } + + request.append("("); + + Set> entries = propMap.entrySet(); + Iterator> it = entries.iterator(); + + int count = 0; + while (it.hasNext()) { + Map.Entry entry = it.next(); + Object value = entry.getValue(); + String propName = entry.getKey(); + + if (count > 0) { + request.append("and "); + } + + request.append(name(propName)); + if (value == null) { + request.append(" is null "); + } else { + request.append(" = ? "); + } + count++; + } + request.append(")"); + } + + /** + * Based on the properties and whether they are null. + *

+ * The null check is required due to the "is null" sql being generated. + *

+ */ + public int queryAutoFetchHash() { + + int hc = AllEqualsExpression.class.getName().hashCode(); + Set> entries = propMap.entrySet(); + Iterator> it = entries.iterator(); + + while (it.hasNext()) { + Map.Entry entry = it.next(); + Object value = entry.getValue(); + String propName = entry.getKey(); + + hc = hc * 31 + propName.hashCode(); + hc = hc * 31 + (value == null ? 0 : 1); + } + + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return queryAutoFetchHash(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java new file mode 100644 index 000000000..e25b9d025 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java @@ -0,0 +1,48 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; + + +class BetweenExpression extends AbstractExpression { + + private static final long serialVersionUID = 2078918165221454910L; + + private static final String BETWEEN = " between "; + + private final Object valueHigh; + + private final Object valueLow; + + BetweenExpression(FilterExprPath pathPrefix, String propertyName, Object valLo, Object valHigh) { + super(pathPrefix, propertyName); + this.valueLow = valLo; + this.valueHigh = valHigh; + } + + public void addBindValues(SpiExpressionRequest request) { + request.addBindValue(valueLow); + request.addBindValue(valueHigh); + } + + public void addSql(SpiExpressionRequest request) { + + request.append(getPropertyName()).append(BETWEEN).append(" ? and ? "); + } + + public int queryAutoFetchHash() { + int hc = BetweenExpression.class.getName().hashCode(); + hc = hc * 31 + propName.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + int hc = valueLow.hashCode(); + hc = hc * 31 + valueHigh.hashCode(); + return hc; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java new file mode 100644 index 000000000..ddff6726b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; + +/** + * Between expression where a value is between two properties. + * + * @author rbygrave + */ +class BetweenPropertyExpression implements SpiExpression { + + private static final long serialVersionUID = 2078918165221454910L; + + private static final String BETWEEN = " between "; + + private final FilterExprPath pathPrefix; + private final String lowProperty; + private final String highProperty; + private final Object value; + + BetweenPropertyExpression(FilterExprPath pathPrefix, String lowProperty, String highProperty, Object value) { + this.pathPrefix = pathPrefix; + this.lowProperty = lowProperty; + this.highProperty = highProperty; + this.value = value; + } + + protected String name(String propName) { + if (pathPrefix == null) { + return propName; + } else { + String path = pathPrefix.getPath(); + if (path == null || path.length() == 0) { + return propName; + } else { + return path + "." + propName; + } + } + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(lowProperty)); + if (elProp != null && elProp.containsMany()) { + manyWhereJoin.add(elProp); + } + + elProp = desc.getElPropertyDeploy(name(highProperty)); + if (elProp != null && elProp.containsMany()) { + manyWhereJoin.add(elProp); + } + } + + public void addBindValues(SpiExpressionRequest request) { + request.addBindValue(value); + } + + public void addSql(SpiExpressionRequest request) { + + request.append(" ? ").append(BETWEEN).append(name(lowProperty)).append(" and ").append(name(highProperty)); + } + + public int queryAutoFetchHash() { + int hc = BetweenPropertyExpression.class.getName().hashCode(); + hc = hc * 31 + lowProperty.hashCode(); + hc = hc * 31 + highProperty.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return value.hashCode(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java new file mode 100644 index 000000000..517185f8c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +class CaseInsensitiveEqualExpression extends AbstractExpression implements LuceneAwareExpression { + + private static final long serialVersionUID = -6406036750998971064L; + + private final String value; + + CaseInsensitiveEqualExpression(FilterExprPath pathPrefix, String propertyName, String value) { + super(pathPrefix, propertyName); + this.value = value.toLowerCase(); + } + + public void addBindValues(SpiExpressionRequest request) { + + ElPropertyValue prop = getElProp(request); + if (prop != null && prop.isDbEncrypted()) { + // bind the key as well as the value + String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue(); + request.addBindValue(encryptKey); + } + + request.addBindValue(value); + } + + public void addSql(SpiExpressionRequest request) { + + String propertyName = getPropertyName(); + String pname = propertyName; + + ElPropertyValue prop = getElProp(request); + if (prop != null && prop.isDbEncrypted()) { + pname = prop.getBeanProperty().getDecryptProperty(propertyName); + } + + request.append("lower(").append(pname).append(") =? "); + } + + public int queryAutoFetchHash() { + int hc = CaseInsensitiveEqualExpression.class.getName().hashCode(); + hc = hc * 31 + propName.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return value.hashCode(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java new file mode 100644 index 000000000..04edff69c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java @@ -0,0 +1,249 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.avaje.ebean.ExampleExpression; +import com.avaje.ebean.LikeType; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * A "Query By Example" type of expression. + *

+ * Pass in an example entity and for each non-null scalar properties an + * expression is added. + *

+ * + *
+ * // create an example bean and set the properties
+ * // with the query parameters you want
+ * Customer example = new Customer();
+ * example.setName("Rob%");
+ * example.setNotes("%something%");
+ * 
+ * List<Customer> list = Ebean.find(Customer.class).where()
+ * // pass the bean into the where() clause
+ *     .exampleLike(example)
+ *     // you can add other expressions to the same query
+ *     .gt("id", 2).findList();
+ * 
+ * 
+ */ +public class DefaultExampleExpression implements SpiExpression, ExampleExpression { + + private static final long serialVersionUID = 1L; + + /** + * The example bean containing the properties. + */ + private final Object entity; + + /** + * Set to true to use case insensitive expressions. + */ + private boolean caseInsensitive; + + /** + * The type of like (RAW, STARTS_WITH, ENDS_WITH etc) + */ + private LikeType likeType; + + /** + * By default zeros are excluded. + */ + private boolean includeZeros; + + /** + * The non null bean properties and found and together added as a list of + * expressions (like or equal to expressions). + */ + private ArrayList list; + + private final FilterExprPath pathPrefix; + + /** + * Construct the query by example expression. + * + * @param entity + * the example entity with non null property values + * @param caseInsensitive + * if true use case insensitive expressions + * @param likeType + * the type of Like wild card used + */ + public DefaultExampleExpression(FilterExprPath pathPrefix, Object entity, boolean caseInsensitive, LikeType likeType) { + this.pathPrefix = pathPrefix; + this.entity = entity; + this.caseInsensitive = caseInsensitive; + this.likeType = likeType; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { + if (list != null) { + for (int i = 0; i < list.size(); i++) { + list.get(i).containsMany(desc, whereManyJoins); + } + } + } + + public ExampleExpression includeZeros() { + includeZeros = true; + return this; + } + + public ExampleExpression caseInsensitive() { + caseInsensitive = true; + return this; + } + + public ExampleExpression useStartsWith() { + likeType = LikeType.STARTS_WITH; + return this; + } + + public ExampleExpression useContains() { + likeType = LikeType.CONTAINS; + return this; + } + + public ExampleExpression useEndsWith() { + likeType = LikeType.ENDS_WITH; + return this; + } + + public ExampleExpression useEqualTo() { + likeType = LikeType.EQUAL_TO; + return this; + } + + /** + * This will return null for this example expression. + */ + public String getPropertyName() { + return null; + } + + /** + * Adds bind values to the request. + */ + public void addBindValues(SpiExpressionRequest request) { + + for (int i = 0; i < list.size(); i++) { + SpiExpression item = list.get(i); + item.addBindValues(request); + } + } + + /** + * Generates and adds the sql to the request. + */ + public void addSql(SpiExpressionRequest request) { + + if (!list.isEmpty()) { + request.append("("); + + for (int i = 0; i < list.size(); i++) { + SpiExpression item = list.get(i); + if (i > 0) { + request.append(" and "); + } + item.addSql(request); + } + + request.append(") "); + } + } + + /** + * Return a hash for autoFetch query identification. + */ + public int queryAutoFetchHash() { + // we have not yet built the list of expressions + // so just based on the class name + return DefaultExampleExpression.class.getName().hashCode(); + } + + /** + * Return a hash for query plan identification. + */ + public int queryPlanHash(BeanQueryRequest request) { + + // this is always called once, and always called before + // addSql() and addBindValues() methods + list = buildExpressions(request); + + int hc = DefaultExampleExpression.class.getName().hashCode(); + + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryPlanHash(request); + } + + return hc; + } + + /** + * Return a hash for the actual bind values used. + */ + public int queryBindHash() { + int hc = DefaultExampleExpression.class.getName().hashCode(); + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryBindHash(); + } + + return hc; + } + + /** + * Build the List of expressions. + */ + private ArrayList buildExpressions(BeanQueryRequest request) { + + ArrayList list = new ArrayList(); + + OrmQueryRequest r = (OrmQueryRequest) request; + BeanDescriptor beanDescriptor = r.getBeanDescriptor(); + + Iterator propIter = beanDescriptor.propertiesAll(); + + while (propIter.hasNext()) { + BeanProperty beanProperty = propIter.next(); + String propName = beanProperty.getName(); + Object value = beanProperty.getValue(entity); + + if (beanProperty.isScalar() && value != null) { + if (value instanceof String) { + list.add(new LikeExpression(pathPrefix, propName, (String) value, caseInsensitive, likeType)); + } else { + if (!includeZeros && isZero(value)) { + // exclude the zero values typically to weed out + // primitive int and long that initialise to 0 + } else { + list.add(new SimpleExpression(pathPrefix, propName, SimpleExpression.Op.EQ, value)); + } + } + } + } + + return list; + } + + /** + * Return true if the value is a numeric zero. + */ + private boolean isZero(Object value) { + if (value instanceof Number) { + Number num = (Number) value; + double doubleValue = num.doubleValue(); + if (doubleValue == 0) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java new file mode 100644 index 000000000..18e4c2837 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -0,0 +1,356 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.avaje.ebean.ExampleExpression; +import com.avaje.ebean.Expression; +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.Junction; +import com.avaje.ebean.LikeType; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.api.SpiExpressionFactory; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Default Expression factory for creating standard expressions. + */ +public class DefaultExpressionFactory implements SpiExpressionFactory { + + private static final Object[] EMPTY_ARRAY = new Object[] {}; + + private final FilterExprPath prefix; + + public DefaultExpressionFactory() { + this(null); + } + + public DefaultExpressionFactory(FilterExprPath prefix) { + this.prefix = prefix; + } + + public ExpressionFactory createExpressionFactory(FilterExprPath prefix) { + return new DefaultExpressionFactory(prefix); + } + + public String getLang() { + return "sql"; + } + + /** + * Equal To - property equal to the given value. + */ + public Expression eq(String propertyName, Object value) { + if (value == null) { + return isNull(propertyName); + } + return new SimpleExpression(prefix, propertyName, SimpleExpression.Op.EQ, value); + } + + /** + * Not Equal To - property not equal to the given value. + */ + public Expression ne(String propertyName, Object value) { + if (value == null) { + return isNotNull(propertyName); + } + return new SimpleExpression(prefix, propertyName, SimpleExpression.Op.NOT_EQ, value); + } + + /** + * Case Insensitive Equal To - property equal to the given value (typically + * using a lower() function to make it case insensitive). + */ + public Expression ieq(String propertyName, String value) { + if (value == null) { + return isNull(propertyName); + } + return new CaseInsensitiveEqualExpression(prefix, propertyName, value); + } + + /** + * Between - property between the two given values. + */ + public Expression between(String propertyName, Object value1, Object value2) { + + return new BetweenExpression(prefix, propertyName, value1, value2); + } + + /** + * Between - value between two given properties. + */ + public Expression betweenProperties(String lowProperty, String highProperty, Object value) { + + return new BetweenPropertyExpression(prefix, lowProperty, highProperty, value); + } + + /** + * Greater Than - property greater than the given value. + */ + public Expression gt(String propertyName, Object value) { + + return new SimpleExpression(prefix, propertyName, SimpleExpression.Op.GT, value); + } + + /** + * Greater Than or Equal to - property greater than or equal to the given + * value. + */ + public Expression ge(String propertyName, Object value) { + + return new SimpleExpression(prefix, propertyName, SimpleExpression.Op.GT_EQ, value); + } + + /** + * Less Than - property less than the given value. + */ + public Expression lt(String propertyName, Object value) { + + return new SimpleExpression(prefix, propertyName, SimpleExpression.Op.LT, value); + } + + /** + * Less Than or Equal to - property less than or equal to the given value. + */ + public Expression le(String propertyName, Object value) { + + return new SimpleExpression(prefix, propertyName, SimpleExpression.Op.LT_EQ, value); + } + + /** + * Is Null - property is null. + */ + public Expression isNull(String propertyName) { + + return new NullExpression(prefix, propertyName, false); + } + + /** + * Is Not Null - property is not null. + */ + public Expression isNotNull(String propertyName) { + + return new NullExpression(prefix, propertyName, true); + } + + /** + * Case insensitive {@link #exampleLike(Object)} + */ + public ExampleExpression iexampleLike(Object example) { + return new DefaultExampleExpression(prefix, example, true, LikeType.RAW); + } + + /** + * Create the query by Example expression which is case sensitive and using + * LikeType.RAW (you need to add you own wildcards % and _). + */ + public ExampleExpression exampleLike(Object example) { + return new DefaultExampleExpression(prefix, example, false, LikeType.RAW); + } + + /** + * Create the query by Example expression specifying more options. + */ + public ExampleExpression exampleLike(Object example, boolean caseInsensitive, LikeType likeType) { + return new DefaultExampleExpression(prefix, example, caseInsensitive, likeType); + } + + /** + * Like - property like value where the value contains the SQL wild card + * characters % (percentage) and _ (underscore). + */ + public Expression like(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, false, LikeType.RAW); + } + + /** + * Case insensitive Like - property like value where the value contains the + * SQL wild card characters % (percentage) and _ (underscore). Typically uses + * a lower() function to make the expression case insensitive. + */ + public Expression ilike(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, true, LikeType.RAW); + } + + /** + * Starts With - property like value%. + */ + public Expression startsWith(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, false, LikeType.STARTS_WITH); + } + + /** + * Case insensitive Starts With - property like value%. Typically uses a + * lower() function to make the expression case insensitive. + */ + public Expression istartsWith(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, true, LikeType.STARTS_WITH); + } + + /** + * Ends With - property like %value. + */ + public Expression endsWith(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, false, LikeType.ENDS_WITH); + } + + /** + * Case insensitive Ends With - property like %value. Typically uses a lower() + * function to make the expression case insensitive. + */ + public Expression iendsWith(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, true, LikeType.ENDS_WITH); + } + + /** + * Contains - property like %value%. + */ + public Expression contains(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, false, LikeType.CONTAINS); + } + + /** + * Case insensitive Contains - property like %value%. Typically uses a lower() + * function to make the expression case insensitive. + */ + public Expression icontains(String propertyName, String value) { + return new LikeExpression(prefix, propertyName, value, true, LikeType.CONTAINS); + } + + /** + * In - property has a value in the array of values. + */ + public Expression in(String propertyName, Object[] values) { + return new InExpression(prefix, propertyName, values); + } + + /** + * In - using a subQuery. + */ + public Expression in(String propertyName, Query subQuery) { + return new InQueryExpression(prefix, propertyName, (SpiQuery) subQuery); + } + + /** + * In - property has a value in the collection of values. + */ + public Expression in(String propertyName, Collection values) { + return new InExpression(prefix, propertyName, values); + } + + /** + * Id Equal to - ID property is equal to the value. + */ + public Expression idEq(Object value) { + return new IdExpression(value); + } + + /** + * Id IN a list of id values. + */ + public Expression idIn(List idList) { + return new IdInExpression(idList); + } + + /** + * All Equal - Map containing property names and their values. + *

+ * Expression where all the property names in the map are equal to the + * corresponding value. + *

+ * + * @param propertyMap + * a map keyed by property names. + */ + public Expression allEq(Map propertyMap) { + return new AllEqualsExpression(prefix, propertyMap); + } + + /** + * Add raw expression with a single parameter. + *

+ * The raw expression should contain a single ? at the location of the + * parameter. + *

+ */ + public Expression raw(String raw, Object value) { + return new RawExpression(raw, new Object[] { value }); + } + + /** + * Add raw expression with an array of parameters. + *

+ * The raw expression should contain the same number of ? as there are + * parameters. + *

+ */ + public Expression raw(String raw, Object[] values) { + return new RawExpression(raw, values); + } + + /** + * Add raw expression with no parameters. + */ + public Expression raw(String raw) { + return new RawExpression(raw, EMPTY_ARRAY); + } + + /** + * And - join two expressions with a logical and. + */ + public Expression and(Expression expOne, Expression expTwo) { + + return new LogicExpression.And(expOne, expTwo); + } + + /** + * Or - join two expressions with a logical or. + */ + public Expression or(Expression expOne, Expression expTwo) { + + return new LogicExpression.Or(expOne, expTwo); + } + + /** + * Negate the expression (prefix it with NOT). + */ + public Expression not(Expression exp) { + + return new NotExpression(exp); + } + + /** + * Return a list of expressions that will be joined by AND's. + */ + public Junction conjunction(Query query) { + + return new JunctionExpression.Conjunction(query, query.where()); + } + + /** + * Return a list of expressions that will be joined by OR's. + */ + public Junction disjunction(Query query) { + + return new JunctionExpression.Disjunction(query, query.where()); + } + + /** + * Return a list of expressions that will be joined by AND's. + */ + public Junction conjunction(Query query, ExpressionList parent) { + + return new JunctionExpression.Conjunction(query, parent); + } + + /** + * Return a list of expressions that will be joined by OR's. + */ + public Junction disjunction(Query query, ExpressionList parent) { + + return new JunctionExpression.Disjunction(query, parent); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java b/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java new file mode 100644 index 000000000..15f0dcb7f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.expression; + +import java.io.Serializable; + +/** + * This is the path prefix for filterMany. + *

+ * The actual path can change due to FetchConfig query joins that proceed + * the query that includes the filterMany. + *

+ * + * @author rbygrave + */ +public class FilterExprPath implements Serializable { + + private static final long serialVersionUID = -6420905565372842018L; + + /** + * The path of the filterMany. + */ + private String path; + + public FilterExprPath(String path){ + this.path = path; + } + + /** + * Trim off leading part of the path due to a + * proceeding (earlier) query join etc. + */ + public void trimPath(int prefixTrim) { + path = path.substring(prefixTrim); + } + + /** + * Return the path. This is a prefix used in the filterMany expressions. + */ + public String getPath() { + return path; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java new file mode 100644 index 000000000..43c203ff9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java @@ -0,0 +1,66 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.util.DefaultExpressionRequest; + +/** + * Slightly redundant as Query.setId() ultimately also does the same job. + */ +class IdExpression implements SpiExpression { + + private static final long serialVersionUID = -3065936341718489842L; + + private final Object value; + + IdExpression(Object value) { + this.value = value; + } + + /** + * Always returns false. + */ + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + } + + public void addBindValues(SpiExpressionRequest request) { + + // 'flatten' EmbeddedId and multiple Id cases + // into an array of the underlying scalar field values + DefaultExpressionRequest r = (DefaultExpressionRequest) request; + Object[] bindIdValues = r.getBeanDescriptor().getBindIdValues(value); + for (int i = 0; i < bindIdValues.length; i++) { + request.addBindValue(bindIdValues[i]); + } + } + + public void addSql(SpiExpressionRequest request) { + + DefaultExpressionRequest r = (DefaultExpressionRequest) request; + String idSql = r.getBeanDescriptor().getIdBinderIdSql(); + + request.append(idSql).append(" "); + } + + /** + * No properties so this is just a unique static number. + */ + public int queryAutoFetchHash() { + // this number is unique for a given bean type + // which is all that is required + return IdExpression.class.getName().hashCode(); + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return value.hashCode(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java new file mode 100644 index 000000000..7026db7f6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java @@ -0,0 +1,86 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.List; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.util.DefaultExpressionRequest; + +/** + * Slightly redundant as Query.setId() ultimately also does the same job. + */ +public class IdInExpression implements SpiExpression { + + private static final long serialVersionUID = 1L; + + private final List idList; + + public IdInExpression(List idList) { + this.idList = idList; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + } + + public void addBindValues(SpiExpressionRequest request) { + + // Bind the Id values including EmbeddedId and multiple Id + + DefaultExpressionRequest r = (DefaultExpressionRequest) request; + BeanDescriptor descriptor = r.getBeanDescriptor(); + IdBinder idBinder = descriptor.getIdBinder(); + + for (int i = 0; i < idList.size(); i++) { + idBinder.addIdInBindValue(request, idList.get(i)); + } + } + + /** + * For use with deleting non attached detail beans during stateless update. + */ + public void addSqlNoAlias(SpiExpressionRequest request) { + + DefaultExpressionRequest r = (DefaultExpressionRequest) request; + BeanDescriptor descriptor = r.getBeanDescriptor(); + IdBinder idBinder = descriptor.getIdBinder(); + + request.append(descriptor.getIdBinder().getBindIdInSql(null)); + String inClause = idBinder.getIdInValueExpr(idList.size()); + request.append(inClause); + } + + public void addSql(SpiExpressionRequest request) { + + DefaultExpressionRequest r = (DefaultExpressionRequest) request; + BeanDescriptor descriptor = r.getBeanDescriptor(); + IdBinder idBinder = descriptor.getIdBinder(); + + request.append(descriptor.getIdBinderInLHSSql()); + String inClause = idBinder.getIdInValueExpr(idList.size()); + request.append(inClause); + } + + /** + * Incorporates the number of Id values to bind. + */ + public int queryAutoFetchHash() { + // this number is unique for a given bean type + // which is all that is required + int hc = IdInExpression.class.getName().hashCode(); + hc = hc * 31 + idList.size(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return idList.hashCode(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java new file mode 100644 index 000000000..dc43a320d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java @@ -0,0 +1,100 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.Collection; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +class InExpression extends AbstractExpression { + + private static final long serialVersionUID = 3150665801693551260L; + + private final Object[] values; + + InExpression(FilterExprPath pathPrefix, String propertyName, Collection coll) { + super(pathPrefix, propertyName); + values = coll.toArray(new Object[coll.size()]); + } + + InExpression(FilterExprPath pathPrefix, String propertyName, Object[] array) { + super(pathPrefix, propertyName); + this.values = array; + } + + public void addBindValues(SpiExpressionRequest request) { + + ElPropertyValue prop = getElProp(request); + if (prop != null && !prop.isAssocId()) { + prop = null; + } + + for (int i = 0; i < values.length; i++) { + if (prop == null) { + request.addBindValue(values[i]); + + } else { + // extract the id values from the bean + Object[] ids = prop.getAssocOneIdValues(values[i]); + if (ids != null) { + for (int j = 0; j < ids.length; j++) { + request.addBindValue(ids[j]); + } + } + } + } + } + + public void addSql(SpiExpressionRequest request) { + + if (values.length == 0) { + // 'no match' for in empty collection + request.append("1=0"); + return; + } + + String propertyName = getPropertyName(); + + ElPropertyValue prop = getElProp(request); + if (prop != null && !prop.isAssocId()) { + prop = null; + } + + if (prop != null) { + request.append(prop.getAssocIdInExpr(propertyName)); + String inClause = prop.getAssocIdInValueExpr(values.length); + request.append(inClause); + + } else { + request.append(propertyName); + request.append(" in (?"); + for (int i = 1; i < values.length; i++) { + request.append(", ").append("?"); + } + + request.append(" ) "); + } + } + + /** + * Based on the number of values in the in clause. + */ + public int queryAutoFetchHash() { + int hc = InExpression.class.getName().hashCode() + 31 * values.length; + hc = hc * 31 + propName.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + int hc = 0; + for (int i = 1; i < values.length; i++) { + hc = 31 * hc + values[i].hashCode(); + } + return hc; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java new file mode 100644 index 000000000..4fa82d4bf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java @@ -0,0 +1,86 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.List; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.query.CQuery; + +/** + * In expression using a sub query. + * + * @authors Mario and Rob + */ +class InQueryExpression extends AbstractExpression { + + private static final long serialVersionUID = 666990277309851644L; + + private final SpiQuery subQuery; + + private transient CQuery compiledSubQuery; + + public InQueryExpression(FilterExprPath pathPrefix, String propertyName, SpiQuery subQuery) { + super(pathPrefix, propertyName); + this.subQuery = subQuery; + } + + public int queryAutoFetchHash() { + int hc = InQueryExpression.class.getName().hashCode(); + hc = hc * 31 + propName.hashCode(); + hc = hc * 31 + subQuery.queryAutofetchHash(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + + // queryPlanHash executes prior to addSql() or addBindValues() + // ... so compiledQuery will exist + compiledSubQuery = compileSubQuery(request); + + int hc = InQueryExpression.class.getName().hashCode(); + hc = hc * 31 + propName.hashCode(); + hc = hc * 31 + subQuery.queryPlanHash(request); + return hc; + } + + /** + * Compile/build the sub query. + */ + private CQuery compileSubQuery(BeanQueryRequest queryRequest) { + + SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer(); + return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction()); + } + + public int queryBindHash() { + return subQuery.queryBindHash(); + } + + public void addSql(SpiExpressionRequest request) { + + String subSelect = compiledSubQuery.getGeneratedSql(); + subSelect = subSelect.replace('\n', ' '); + + String propertyName = getPropertyName(); + request.append(" ("); + request.append(propertyName); + request.append(") in ("); + request.append(subSelect); + request.append(") "); + } + + public void addBindValues(SpiExpressionRequest request) { + + List bindParams = compiledSubQuery.getPredicates().getWhereExprBindValues(); + + if (bindParams == null) { + return; + } + + for (int i = 0; i < bindParams.size(); i++) { + request.addBindValue(bindParams.get(i)); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java new file mode 100644 index 000000000..c8d0b7af1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -0,0 +1,421 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.Expression; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.FutureList; +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.Junction; +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.PagingList; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryListener; +import com.avaje.ebean.QueryResultVisitor; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.util.DefaultExpressionList; + +/** + * Junction implementation. + */ +abstract class JunctionExpression implements Junction, SpiExpression, ExpressionList { + + private static final long serialVersionUID = -7422204102750462676L; + + private static final String OR = " or "; + + private static final String AND = " and "; + + static class Conjunction extends JunctionExpression { + + private static final long serialVersionUID = -645619859900030678L; + + Conjunction(com.avaje.ebean.Query query, ExpressionList parent) { + super(AND, query, parent); + } + } + + static class Disjunction extends JunctionExpression { + + private static final long serialVersionUID = -8464470066692221413L; + + Disjunction(com.avaje.ebean.Query query, ExpressionList parent) { + super(OR, query, parent); + } + } + + // private final ArrayList list = new + // ArrayList(); + private final DefaultExpressionList exprList; + + private final String joinType; + + JunctionExpression(String joinType, com.avaje.ebean.Query query, ExpressionList parent) { + this.joinType = joinType; + this.exprList = new DefaultExpressionList(query, parent); + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + List list = exprList.internalList(); + + for (int i = 0; i < list.size(); i++) { + list.get(i).containsMany(desc, manyWhereJoin); + } + } + + public Junction add(Expression item) { + SpiExpression i = (SpiExpression) item; + exprList.add(i); + return this; + } + + public Junction addAll(ExpressionList addList) { + exprList.addAll(addList); + return this; + } + + public void addBindValues(SpiExpressionRequest request) { + + List list = exprList.internalList(); + + for (int i = 0; i < list.size(); i++) { + SpiExpression item = list.get(i); + item.addBindValues(request); + } + } + + public void addSql(SpiExpressionRequest request) { + + List list = exprList.internalList(); + + if (!list.isEmpty()) { + request.append("("); + + for (int i = 0; i < list.size(); i++) { + SpiExpression item = list.get(i); + if (i > 0) { + request.append(joinType); + } + item.addSql(request); + } + + request.append(") "); + } + } + + /** + * Based on Junction type and all the expression contained. + */ + public int queryAutoFetchHash() { + int hc = JunctionExpression.class.getName().hashCode(); + hc = hc * 31 + joinType.hashCode(); + + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryAutoFetchHash(); + } + + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + int hc = JunctionExpression.class.getName().hashCode(); + hc = hc * 31 + joinType.hashCode(); + + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryPlanHash(request); + } + + return hc; + } + + public int queryBindHash() { + int hc = JunctionExpression.class.getName().hashCode(); + + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryBindHash(); + } + + return hc; + } + + public ExpressionList endJunction() { + return exprList.endJunction(); + } + + public ExpressionList allEq(Map propertyMap) { + return exprList.allEq(propertyMap); + } + + public ExpressionList and(Expression expOne, Expression expTwo) { + return exprList.and(expOne, expTwo); + } + + public ExpressionList between(String propertyName, Object value1, Object value2) { + return exprList.between(propertyName, value1, value2); + } + + public ExpressionList betweenProperties(String lowProperty, String highProperty, Object value) { + return exprList.betweenProperties(lowProperty, highProperty, value); + } + + public Junction conjunction() { + return exprList.conjunction(); + } + + public ExpressionList contains(String propertyName, String value) { + return exprList.contains(propertyName, value); + } + + public Junction disjunction() { + return exprList.disjunction(); + } + + public ExpressionList endsWith(String propertyName, String value) { + return exprList.endsWith(propertyName, value); + } + + public ExpressionList eq(String propertyName, Object value) { + return exprList.eq(propertyName, value); + } + + public ExpressionList exampleLike(Object example) { + return exprList.exampleLike(example); + } + + public ExpressionList filterMany(String prop) { + throw new RuntimeException("filterMany not allowed on Junction expression list"); + } + + public FutureIds findFutureIds() { + return exprList.findFutureIds(); + } + + public FutureList findFutureList() { + return exprList.findFutureList(); + } + + public FutureRowCount findFutureRowCount() { + return exprList.findFutureRowCount(); + } + + public List findIds() { + return exprList.findIds(); + } + + public void findVisit(QueryResultVisitor visitor) { + exprList.findVisit(visitor); + } + + public QueryIterator findIterate() { + return exprList.findIterate(); + } + + public List findList() { + return exprList.findList(); + } + + public Map findMap() { + return exprList.findMap(); + } + + public Map findMap(String keyProperty, Class keyType) { + return exprList.findMap(keyProperty, keyType); + } + + public PagingList findPagingList(int pageSize) { + return exprList.findPagingList(pageSize); + } + + public int findRowCount() { + return exprList.findRowCount(); + } + + public Set findSet() { + return exprList.findSet(); + } + + public T findUnique() { + return exprList.findUnique(); + } + + public ExpressionList ge(String propertyName, Object value) { + return exprList.ge(propertyName, value); + } + + public ExpressionList gt(String propertyName, Object value) { + return exprList.gt(propertyName, value); + } + + public ExpressionList having() { + throw new RuntimeException("having() not allowed on Junction expression list"); + } + + public ExpressionList icontains(String propertyName, String value) { + return exprList.icontains(propertyName, value); + } + + public ExpressionList idEq(Object value) { + return exprList.idEq(value); + } + + public ExpressionList idIn(List idValues) { + return exprList.idIn(idValues); + } + + public ExpressionList iendsWith(String propertyName, String value) { + return exprList.iendsWith(propertyName, value); + } + + public ExpressionList ieq(String propertyName, String value) { + return exprList.ieq(propertyName, value); + } + + public ExpressionList iexampleLike(Object example) { + return exprList.iexampleLike(example); + } + + public ExpressionList ilike(String propertyName, String value) { + return exprList.ilike(propertyName, value); + } + + public ExpressionList in(String propertyName, Collection values) { + return exprList.in(propertyName, values); + } + + public ExpressionList in(String propertyName, Object... values) { + return exprList.in(propertyName, values); + } + + public ExpressionList in(String propertyName, com.avaje.ebean.Query subQuery) { + return exprList.in(propertyName, subQuery); + } + + public ExpressionList isNotNull(String propertyName) { + return exprList.isNotNull(propertyName); + } + + public ExpressionList isNull(String propertyName) { + return exprList.isNull(propertyName); + } + + public ExpressionList istartsWith(String propertyName, String value) { + return exprList.istartsWith(propertyName, value); + } + + public com.avaje.ebean.Query join(String assocProperty, String assocProperties) { + return exprList.join(assocProperty, assocProperties); + } + + public com.avaje.ebean.Query join(String assocProperties) { + return exprList.join(assocProperties); + } + + public ExpressionList le(String propertyName, Object value) { + return exprList.le(propertyName, value); + } + + public ExpressionList like(String propertyName, String value) { + return exprList.like(propertyName, value); + } + + public ExpressionList lt(String propertyName, Object value) { + return exprList.lt(propertyName, value); + } + + public ExpressionList ne(String propertyName, Object value) { + return exprList.ne(propertyName, value); + } + + public ExpressionList not(Expression exp) { + return exprList.not(exp); + } + + public ExpressionList or(Expression expOne, Expression expTwo) { + return exprList.or(expOne, expTwo); + } + + public OrderBy order() { + return exprList.order(); + } + + public com.avaje.ebean.Query order(String orderByClause) { + return exprList.order(orderByClause); + } + + public OrderBy orderBy() { + return exprList.orderBy(); + } + + public com.avaje.ebean.Query orderBy(String orderBy) { + return exprList.orderBy(orderBy); + } + + public com.avaje.ebean.Query query() { + return exprList.query(); + } + + public ExpressionList raw(String raw, Object value) { + return exprList.raw(raw, value); + } + + public ExpressionList raw(String raw, Object[] values) { + return exprList.raw(raw, values); + } + + public ExpressionList raw(String raw) { + return exprList.raw(raw); + } + + public com.avaje.ebean.Query select(String properties) { + return exprList.select(properties); + } + + public com.avaje.ebean.Query setBackgroundFetchAfter(int backgroundFetchAfter) { + return exprList.setBackgroundFetchAfter(backgroundFetchAfter); + } + + public com.avaje.ebean.Query setFirstRow(int firstRow) { + return exprList.setFirstRow(firstRow); + } + + public com.avaje.ebean.Query setListener(QueryListener queryListener) { + return exprList.setListener(queryListener); + } + + public com.avaje.ebean.Query setMapKey(String mapKey) { + return exprList.setMapKey(mapKey); + } + + public com.avaje.ebean.Query setMaxRows(int maxRows) { + return exprList.setMaxRows(maxRows); + } + + public com.avaje.ebean.Query setOrderBy(String orderBy) { + return exprList.setOrderBy(orderBy); + } + + public com.avaje.ebean.Query setUseCache(boolean useCache) { + return exprList.setUseCache(useCache); + } + + public ExpressionList startsWith(String propertyName, String value) { + return exprList.startsWith(propertyName, value); + } + + public ExpressionList where() { + return exprList.where(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java new file mode 100644 index 000000000..d0bec6978 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java @@ -0,0 +1,98 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.LikeType; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +class LikeExpression extends AbstractExpression implements LuceneAwareExpression { + + private static final long serialVersionUID = -5398151809111172380L; + + private final String val; + + private final boolean caseInsensitive; + + private final LikeType type; + + LikeExpression(FilterExprPath pathPrefix, String propertyName, String value, boolean caseInsensitive, LikeType type) { + super(pathPrefix, propertyName); + this.caseInsensitive = caseInsensitive; + this.type = type; + this.val = value; + } + + public void addBindValues(SpiExpressionRequest request) { + + ElPropertyValue prop = getElProp(request); + if (prop != null && prop.isDbEncrypted()) { + // bind the key as well as the value + String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue(); + request.addBindValue(encryptKey); + } + + String bindValue = getValue(val, caseInsensitive, type); + request.addBindValue(bindValue); + } + + public void addSql(SpiExpressionRequest request) { + + String propertyName = getPropertyName(); + String pname = propertyName; + + ElPropertyValue prop = getElProp(request); + if (prop != null && prop.isDbEncrypted()) { + pname = prop.getBeanProperty().getDecryptProperty(propertyName); + } + if (caseInsensitive) { + request.append("lower(").append(pname).append(")"); + } else { + request.append(pname); + } + if (type.equals(LikeType.EQUAL_TO)) { + request.append(" = ? "); + } else { + request.append(" like ? "); + } + } + + /** + * Based on caseInsensitive and the property name. + */ + public int queryAutoFetchHash() { + int hc = LikeExpression.class.getName().hashCode(); + hc = hc * 31 + (caseInsensitive ? 0 : 1); + hc = hc * 31 + propName.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return val.hashCode(); + } + + private static String getValue(String value, boolean caseInsensitive, LikeType type) { + if (caseInsensitive) { + value = value.toLowerCase(); + } + switch (type) { + case RAW: + return value; + case STARTS_WITH: + return value + "%"; + case ENDS_WITH: + return "%" + value; + case CONTAINS: + return "%" + value + "%"; + case EQUAL_TO: + return value; + + default: + throw new RuntimeException("LikeType " + type + " missed?"); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java new file mode 100644 index 000000000..a8e813227 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java @@ -0,0 +1,92 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.Expression; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * A logical And or Or for joining two expressions. + */ +abstract class LogicExpression implements SpiExpression { + + private static final long serialVersionUID = 616860781960645251L; + + static final String AND = " and "; + static final String OR = " or "; + + static class And extends LogicExpression { + + private static final long serialVersionUID = -3832889676798526444L; + + And(Expression expOne, Expression expTwo) { + super(AND, expOne, expTwo); + } + } + + static class Or extends LogicExpression { + + private static final long serialVersionUID = -6871993143194094819L; + + Or(Expression expOne, Expression expTwo) { + super(OR, expOne, expTwo); + } + } + + private final SpiExpression expOne; + + private final SpiExpression expTwo; + + private final String joinType; + + LogicExpression(String joinType, Expression expOne, Expression expTwo) { + this.joinType = joinType; + this.expOne = (SpiExpression) expOne; + this.expTwo = (SpiExpression) expTwo; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + expOne.containsMany(desc, manyWhereJoin); + expTwo.containsMany(desc, manyWhereJoin); + } + + public void addBindValues(SpiExpressionRequest request) { + expOne.addBindValues(request); + expTwo.addBindValues(request); + } + + public void addSql(SpiExpressionRequest request) { + + request.append("("); + expOne.addSql(request); + request.append(joinType); + expTwo.addSql(request); + request.append(") "); + } + + /** + * Based on the joinType plus the two expressions. + */ + public int queryAutoFetchHash() { + int hc = LogicExpression.class.getName().hashCode() + joinType.hashCode(); + hc = hc * 31 + expOne.queryAutoFetchHash(); + hc = hc * 31 + expTwo.queryAutoFetchHash(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + int hc = LogicExpression.class.getName().hashCode() + joinType.hashCode(); + hc = hc * 31 + expOne.queryPlanHash(request); + hc = hc * 31 + expTwo.queryPlanHash(request); + return hc; + } + + public int queryBindHash() { + int hc = expOne.queryBindHash(); + hc = hc * 31 + expTwo.queryBindHash(); + return hc; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java new file mode 100644 index 000000000..3a344cfa8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LuceneAwareExpression.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.expression; + +/** + * Marker interface for lucene aware expressions. + * + * @author rbygrave + */ +public interface LuceneAwareExpression { + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java new file mode 100644 index 000000000..e17885ada --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java @@ -0,0 +1,55 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.Expression; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +final class NotExpression implements SpiExpression, LuceneAwareExpression { + + private static final long serialVersionUID = 5648926732402355781L; + + private static final String NOT = "not ("; + + private final SpiExpression exp; + + NotExpression(Expression exp) { + this.exp = (SpiExpression) exp; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + exp.containsMany(desc, manyWhereJoin); + } + + public void addBindValues(SpiExpressionRequest request) { + exp.addBindValues(request); + } + + public void addSql(SpiExpressionRequest request) { + request.append(NOT); + exp.addSql(request); + request.append(") "); + } + + /** + * Based on the expression. + */ + public int queryAutoFetchHash() { + int hc = NotExpression.class.getName().hashCode(); + hc = hc * 31 + exp.queryAutoFetchHash(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + int hc = NotExpression.class.getName().hashCode(); + hc = hc * 31 + exp.queryPlanHash(request); + return hc; + } + + public int queryBindHash() { + return exp.queryBindHash(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java new file mode 100644 index 000000000..e5f80d1e0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java @@ -0,0 +1,58 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + + +/** + * Slightly redundant as Query.setId() ultimately also does the same job. + */ +class NullExpression extends AbstractExpression { + + private static final long serialVersionUID = 4246991057451128269L; + + private final boolean notNull; + + NullExpression(FilterExprPath pathPrefix, String propertyName, boolean notNull) { + super(pathPrefix, propertyName); + this.notNull = notNull; + } + + public void addBindValues(SpiExpressionRequest request) { + + } + + public void addSql(SpiExpressionRequest request) { + + String propertyName = getPropertyName(); + + String nullExpr = notNull ? " is not null " : " is null "; + + ElPropertyValue prop = getElProp(request); + if (prop != null && prop.isAssocId()){ + request.append(prop.getAssocOneIdExpr(propertyName, nullExpr)); + return; + } + + request.append(propertyName).append(nullExpr); + } + + /** + * Based on notNull flag and the propertyName. + */ + public int queryAutoFetchHash() { + int hc = NullExpression.class.getName().hashCode(); + hc = hc * 31 + (notNull ? 1 : 0); + hc = hc * 31 + propName.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return (notNull ? 1 : 0); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java b/src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java new file mode 100644 index 000000000..b3fecffb2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/PersistenceLuceneParseException.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.expression; + +import javax.persistence.PersistenceException; + +/** + * Exception used to wrap Lucene parsing exceptions. + */ +public class PersistenceLuceneParseException extends PersistenceException { + + private static final long serialVersionUID = 838790249273928392L; + + public PersistenceLuceneParseException(Throwable e){ + super(e); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java new file mode 100644 index 000000000..048c31f39 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java @@ -0,0 +1,54 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +class RawExpression implements SpiExpression { + + private static final long serialVersionUID = 7973903141340334606L; + + private final String sql; + + private final Object[] values; + + RawExpression(String sql, Object[] values) { + this.sql = sql; + this.values = values; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + } + + public void addBindValues(SpiExpressionRequest request) { + if (values != null) { + for (int i = 0; i < values.length; i++) { + request.addBindValue(values[i]); + } + } + } + + public void addSql(SpiExpressionRequest request) { + request.append(sql); + } + + /** + * Based on the sql. + */ + public int queryAutoFetchHash() { + int hc = RawExpression.class.getName().hashCode(); + hc = hc * 31 + sql.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return sql.hashCode(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java new file mode 100644 index 000000000..49e0fbe7d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java @@ -0,0 +1,113 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +public class SimpleExpression extends AbstractExpression implements LuceneAwareExpression { + + private static final long serialVersionUID = -382881395755603790L; + + enum Op { + EQ(" = ? ", " = "), NOT_EQ(" <> ? ", " <> "), LT(" < ? ", " < "), LT_EQ(" <= ? ", " <= "), GT(" > ? ", " > "), GT_EQ(" >= ? ", " >= "); + + String exp; + String shortDesc; + + Op(String exp, String shortDesc) { + this.exp = exp; + this.shortDesc = shortDesc; + } + + public String bind() { + return exp; + } + + public String shortDesc() { + return shortDesc; + } + } + + private final Op type; + + private final Object value; + + public SimpleExpression(FilterExprPath pathPrefix, String propertyName, Op type, Object value) { + super(pathPrefix, propertyName); + this.type = type; + this.value = value; + } + + public boolean isOpEquals() { + return Op.EQ.equals(type); + } + + public void addBindValues(SpiExpressionRequest request) { + + ElPropertyValue prop = getElProp(request); + if (prop != null) { + if (prop.isAssocId()) { + Object[] ids = prop.getAssocOneIdValues(value); + if (ids != null) { + for (int i = 0; i < ids.length; i++) { + request.addBindValue(ids[i]); + } + } + return; + } + if (prop.isDbEncrypted()) { + // bind the key as well as the value + String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue(); + request.addBindValue(encryptKey); + } else if (prop.isLocalEncrypted()) { + // not supporting this for equals (but probably could) + // prop.getBeanProperty().getScalarType(); + + } + } + + request.addBindValue(value); + } + + public void addSql(SpiExpressionRequest request) { + + String propertyName = getPropertyName(); + + ElPropertyValue prop = getElProp(request); + if (prop != null) { + if (prop.isAssocId()) { + request.append(prop.getAssocOneIdExpr(propertyName, type.bind())); + return; + } + if (prop.isDbEncrypted()) { + String dsql = prop.getBeanProperty().getDecryptSql(); + request.append(dsql).append(type.bind()); + return; + } + } + request.append(propertyName).append(type.bind()); + } + + /** + * Based on the type and propertyName. + */ + public int queryAutoFetchHash() { + int hc = SimpleExpression.class.getName().hashCode(); + hc = hc * 31 + propName.hashCode(); + hc = hc * 31 + type.name().hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return value.hashCode(); + } + + public Object getValue() { + return value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/package.html b/src/main/java/com/avaje/ebeaninternal/server/expression/package.html new file mode 100644 index 000000000..b06ccffc1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/package.html @@ -0,0 +1,36 @@ + + + + Collection objects for lazy loading + + +Expressions for building WHERE clauses. + +

+You will most likely use expressions directly off the ExpressionList. +

+
+  Query<Order> query = Ebean.createQuery(Order.class)
+    .where()
+      .like("customer.name","rob%")
+      .gt("orderDate",lastWeek);
+    
+  List<Order> orderList = query.findList();
+  ...
+
+

+In the code above the LIKE and GREATER THAN Expressions are added to the where clause. +The way this works is that where() returns an ExpressionList which has methods on +it to create the standard expressions (EQUAL TO, LIKE etc). +

+

+I expect most people to add their expressions in this way. The Expr expression factory +object also has the ability to create the standard expressions. +

+

+You can build your own Expression objects by implementing the Expression interface. +

+ + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java b/src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java new file mode 100644 index 000000000..e885f3d4b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java @@ -0,0 +1,43 @@ +package com.avaje.ebeaninternal.server.idgen; + +import java.util.UUID; + +import com.avaje.ebean.Transaction; +import com.avaje.ebean.config.dbplatform.IdGenerator; + +/** + * IdGenerator for java util UUID. + */ +public class UuidIdGenerator implements IdGenerator { + + /** + * Return UUID from UUID.randomUUID(); + */ + public Object nextId(Transaction t) { + return UUID.randomUUID(); + } + + /** + * Returns "uuid". + */ + public String getName() { + return "uuid"; + } + + /** + * Returns false. + */ + public boolean isDbSequence() { + return false; + } + + /** + * Ignored for UUID as not required as a performance optimisation. + */ + public void preAllocateIds(int allocateSize) { + // ignored + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/idgen/package.html b/src/main/java/com/avaje/ebeaninternal/server/idgen/package.html new file mode 100644 index 000000000..6e97437be --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/idgen/package.html @@ -0,0 +1,11 @@ + + + + Id Generation + + +Id Generation (Sequences, UUIDGen etc) + + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/jdbc/OraclePstmtBatch.java b/src/main/java/com/avaje/ebeaninternal/server/jdbc/OraclePstmtBatch.java new file mode 100644 index 000000000..7f9c670b2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jdbc/OraclePstmtBatch.java @@ -0,0 +1,140 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.jdbc; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.config.PstmtDelegate; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.server.core.PstmtBatch; + + +/** + * Oracle specific handling of JDBC batching. + *

+ * I guess some people don't need to follow the jdbc specification. + *

+ * + * @author rbygrave + * @author imario + */ +public class OraclePstmtBatch implements PstmtBatch { + + private final PstmtDelegate pstmtDelegate; + + /** + * OraclePreparedStatement.setExecuteBatch() method. + */ + private static final Method METHOD_SET_EXECUTE_BATCH; + + /** + * OraclePreparedStatement.sendBatch() method. + */ + private static final Method METHOD_SEND_BATCH; + + private static final RuntimeException INIT_EXCEPTION; + + static { + RuntimeException initException = null; + Method mSetExecuteBatch = null; + Method mSendBatch = null; + + try { + Class ops = ClassUtil.forName("oracle.jdbc.OraclePreparedStatement"); + + mSetExecuteBatch = ops.getMethod("setExecuteBatch", new Class[] { int.class }); + mSendBatch = ops.getMethod("sendBatch"); + + } catch (NoSuchMethodException e) { + initException = new RuntimeException("problems initializing oracle reflection", e); + initException.fillInStackTrace(); + + } catch (ClassNotFoundException e) { + initException = new RuntimeException("problems initializing oracle reflection", e); + initException.fillInStackTrace(); + } + + INIT_EXCEPTION = initException; + METHOD_SET_EXECUTE_BATCH = mSetExecuteBatch; + METHOD_SEND_BATCH = mSendBatch; + } + + public OraclePstmtBatch(PstmtDelegate pstmtDelegate) { + this.pstmtDelegate = pstmtDelegate; + } + + public void setBatchSize(PreparedStatement pstmt, int batchSize) { + if (INIT_EXCEPTION != null) { + throw INIT_EXCEPTION; + } + + try { + // invoke setExecuteBatch(batchSize+1); + METHOD_SET_EXECUTE_BATCH.invoke(pstmtDelegate.unwrap(pstmt), batchSize+1); + } catch (IllegalAccessException e) { + String m = "Error with Oracle setExecuteBatch "+(batchSize+1); + throw new RuntimeException(m, e); + } catch (InvocationTargetException e) { + String m = "Error with Oracle setExecuteBatch "+(batchSize+1); + throw new RuntimeException(m, e); + } + } + + /** + * Simply calls standard pstmt.addBatch(). + */ + public void addBatch(PreparedStatement pstmt) throws SQLException { + pstmt.executeUpdate(); + } + + public int executeBatch(PreparedStatement pstmt, int expectedRows, String sql, boolean occCheck) throws SQLException { + + if (INIT_EXCEPTION != null) { + throw INIT_EXCEPTION; + } + + int rows; + try { + // invoke sendBatch(); + rows = ((Integer) METHOD_SEND_BATCH.invoke(pstmtDelegate.unwrap(pstmt))).intValue(); + + } catch (IllegalAccessException e) { + String msg = "Error invoking Oracle sendBatch method via reflection"; + throw new PersistenceException(msg,e); + + } catch (InvocationTargetException e) { + String msg = "Error invoking Oracle sendBatch method via reflection"; + throw new PersistenceException(msg, e); + } + if (occCheck && rows != expectedRows) { + String msg = "Batch execution expected "+expectedRows+" but got "+rows+" sql:"+sql; + throw new OptimisticLockException(msg); + } + + return rows; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/jdbc/StandardPstmtDelegate.java b/src/main/java/com/avaje/ebeaninternal/server/jdbc/StandardPstmtDelegate.java new file mode 100644 index 000000000..3506e87cc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jdbc/StandardPstmtDelegate.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.jdbc; + +import java.sql.PreparedStatement; + +import com.avaje.ebean.config.PstmtDelegate; +import com.avaje.ebeaninternal.server.lib.sql.ExtendedPreparedStatement; + +/** + * Implementation of PstmtDelegate from Ebean's own + * DataSource. + * + * @author rbygrave + */ +public class StandardPstmtDelegate implements PstmtDelegate { + + /** + * Unwrap the PreparedStatement from Ebean's DataSource implementation. + */ + public PreparedStatement unwrap(PreparedStatement pstmt) { + + return ((ExtendedPreparedStatement)pstmt).getDelegate(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/jdbc/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/jdbc/package-info.java new file mode 100644 index 000000000..cca31a385 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jdbc/package-info.java @@ -0,0 +1,4 @@ +/** + * Fixes for Oracle JDBC driver issues etc. + */ +package com.avaje.ebeaninternal.server.jdbc; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetch.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetch.java new file mode 100644 index 000000000..49884f11e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetch.java @@ -0,0 +1,121 @@ +package com.avaje.ebeaninternal.server.jmx; + +import java.util.logging.Logger; + +import com.avaje.ebean.AdminAutofetch; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.config.AutofetchMode; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; + +/** + * Implementation of the AutoFetchControl. + *

+ * This is accessible via {@link EbeanServer#getAdminAutofetch()} or via JMX + * MBeans. + *

+ */ +public class MAdminAutofetch implements MAdminAutofetchMBean, AdminAutofetch { + + final Logger logger = Logger.getLogger(MAdminAutofetch.class.getName()); + + final AutoFetchManager autoFetchManager; + + final String modeOptions; + + public MAdminAutofetch(AutoFetchManager autoFetchListener) { + this.autoFetchManager = autoFetchListener; + this.modeOptions = AutofetchMode.DEFAULT_OFF + ", " + + AutofetchMode.DEFAULT_ON + ", " + + AutofetchMode.DEFAULT_ONIFEMPTY; + } + + public boolean isQueryTuning() { + return autoFetchManager.isQueryTuning(); + } + + public void setQueryTuning(boolean enable) { + autoFetchManager.setQueryTuning(enable); + } + + public boolean isProfiling() { + return autoFetchManager.isProfiling(); + } + + public void setProfiling(boolean enable) { + autoFetchManager.setProfiling(enable); + } + + public String getModeOptions() { + return modeOptions; + } + + public String getMode() { + return autoFetchManager.getMode().name(); + } + + public void setMode(String implicitMode) { + try { + AutofetchMode mode = AutofetchMode.valueOf(implicitMode); + autoFetchManager.setMode(mode); + } catch (Exception e) { + logger.info("Invalid implicit mode attempted "+e.getMessage()); + } + } + + public String collectUsageViaGC() { + return autoFetchManager.collectUsageViaGC(-1); + } + + public double getProfilingRate() { + return autoFetchManager.getProfilingRate(); + } + + public void setProfilingRate(double rate) { + autoFetchManager.setProfilingRate(rate); + } + + public int getProfilingMin() { + return autoFetchManager.getProfilingMin(); + } + + public int getProfilingBase() { + return autoFetchManager.getProfilingBase(); + } + + public void setProfilingMin(int profilingMin) { + autoFetchManager.setProfilingMin(profilingMin); + } + + public void setProfilingBase(int profilingMax) { + autoFetchManager.setProfilingBase(profilingMax); + } + + public String updateTunedQueryInfo() { + return autoFetchManager.updateTunedQueryInfo(); + } + + public int clearProfilingInfo() { + return autoFetchManager.clearProfilingInfo(); + } + + public int clearTunedQueryInfo() { + return autoFetchManager.clearTunedQueryInfo(); + } + + public void clearQueryStatistics() { + autoFetchManager.clearQueryStatistics(); + } + + public int getTotalProfileSize() { + return autoFetchManager.getTotalProfileSize(); + } + + public int getTotalTunedQueryCount() { + return autoFetchManager.getTotalTunedQueryCount(); + } + + public int getTotalTunedQuerySize() { + return autoFetchManager.getTotalTunedQuerySize(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetchMBean.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetchMBean.java new file mode 100644 index 000000000..49716d500 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminAutofetchMBean.java @@ -0,0 +1,145 @@ +package com.avaje.ebeaninternal.server.jmx; + +import com.avaje.ebean.Query; + +public interface MAdminAutofetchMBean { + + /** + * Return true if profiling is enabled. + */ + public boolean isProfiling(); + + /** + * Set to true to enable profiling. + */ + public void setProfiling(boolean enable); + + /** + * Return true if autoFetch is enabled. + */ + public boolean isQueryTuning(); + + /** + * Set to true to enable autoFetch. + */ + public void setQueryTuning(boolean enable); + + /** + * This controls whether autoFetch is used when it has not + * been explicitly set on a query via {@link Query#setAutoFetch(boolean)}. + */ + public String getMode(); + + /** + * List of the valid implicit modes that can be used. + */ + public String getModeOptions(); + + /** + * Set the auto fetch mode used when a query has not had {@link Query#setAutoFetch(boolean)}. + */ + public void setMode(String mode); + + /** + * Return the max number of queries profiled (per query point). + *

+ * The number of queries profiled is collected per query point. Once a query + * point has profiled this number of queries it does not profile any more. + *

+ */ + public int getProfilingBase(); + + /** + * Set a max number of queries to profile per query point. + *

+ * This number should provide a level of confidence that no more profiling + * is required for this query point. + *

+ */ + public void setProfilingBase(int profilingMaxThreshold); + + /** + * Returns the rate which profiling is collected. + * This is an int between 0 and 100. + */ + public double getProfilingRate(); + + /** + * Set the rate at which profiling is collected after the base. + * + * @param rate a int between 0 and 100. + */ + public void setProfilingRate(double rate); + + /** + * Return the minimum number of queries profiled before autoFetch will start + * automatically tuning the queries. + *

+ * This could be one which means start autoFetch tuning after the first + * profiling information is collected. + *

+ */ + public int getProfilingMin(); + + /** + * Set the minimum number of queries profiled per query point before + * autoFetch will automatically tune the queries. + *

+ * Increasing this number will mean more profiling is collected before + * autoFetch starts tuning the query. + *

+ */ + public void setProfilingMin(int autoFetchMinThreshold); + + /** + * Fire a garbage collection (hint to the JVM). Assuming garbage collection + * fires this will gather the usage profiling information. + */ + public String collectUsageViaGC(); + + /** + * This will take the current profiling information and update the "tuned query + * detail". + *

+ * This is done periodically and can also be manually invoked. + *

+ */ + public String updateTunedQueryInfo(); + + /** + * Clear all the tuned query info. + *

+ * Should only need do this for testing and playing around. + *

+ * @return the amount of tuned query information cleared. + */ + public int clearTunedQueryInfo(); + + /** + * Clear all the profiling information. + *

+ * This means the profiling information will need to be re-gathered. + *

+ *

+ * Should only need do this for testing and playing around. + *

+ * @return the amount of profiled information cleared. + */ + public int clearProfilingInfo(); + + /** + * Return the number of queries tuned by AutoFetch. + */ + public int getTotalTunedQueryCount(); + + /** + * Return the size of the TuneQuery map. + */ + public int getTotalTunedQuerySize(); + + /** + * Return the size of the profile map. + */ + public int getTotalProfileSize(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLogging.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLogging.java new file mode 100644 index 000000000..d3ab6c1a3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLogging.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.jmx; + +import com.avaje.ebean.AdminLogging; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.LogLevel; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.transaction.TransactionManager; + +/** + * Implementation of the LogControl. + *

+ * This is accessible via {@link EbeanServer#getAdminLogging()} or via JMX MBean. + *

+ */ +public class MAdminLogging implements MAdminLoggingMBean, AdminLogging { + + private final TransactionManager transactionManager; + + private boolean debugSql; + private boolean debugLazyLoad; + + /** + * Configure from serverConfig properties. + */ + public MAdminLogging(ServerConfig serverConfig, TransactionManager txManager) { + + this.transactionManager = txManager; + this.debugSql = serverConfig.isDebugSql(); + this.debugLazyLoad = serverConfig.isDebugLazyLoad(); + } + + public void setLogLevel(LogLevel logLevel){ + transactionManager.setTransactionLogLevel(logLevel); + } + + public LogLevel getLogLevel() { + return transactionManager.getTransactionLogLevel(); + } + + public boolean isDebugGeneratedSql() { + return debugSql; + } + + public void setDebugGeneratedSql(boolean debugSql) { + this.debugSql = debugSql; + } + + public boolean isDebugLazyLoad() { + return debugLazyLoad; + } + + public void setDebugLazyLoad(boolean debugLazyLoad) { + this.debugLazyLoad = debugLazyLoad; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLoggingMBean.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLoggingMBean.java new file mode 100644 index 000000000..44c3e3c51 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jmx/MAdminLoggingMBean.java @@ -0,0 +1,37 @@ +package com.avaje.ebeaninternal.server.jmx; + +import com.avaje.ebean.LogLevel; + +public interface MAdminLoggingMBean { + + /** + * The current log level . + */ + public LogLevel getLogLevel(); + + /** + * Set the log level for native sql queries. + */ + public void setLogLevel(LogLevel logLevel); + + /** + * If true Log generated sql to the console. + */ + public boolean isDebugGeneratedSql(); + + /** + * Set to true to Log generated sql to the console. + */ + public void setDebugGeneratedSql(boolean debugSql); + + /** + * Return true if lazy loading should be debugged. + */ + public boolean isDebugLazyLoad(); + + /** + * Set the debugging on lazy loading. + */ + public void setDebugLazyLoad(boolean debugLazyLoad); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/jmx/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/jmx/package-info.java new file mode 100644 index 000000000..a706f9346 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/jmx/package-info.java @@ -0,0 +1,4 @@ +/** + * JMX MBeans. + */ +package com.avaje.ebeaninternal.server.jmx; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapOrmQuery.java new file mode 100644 index 000000000..ecc9e7194 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapOrmQuery.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebeaninternal.server.querydefn.DefaultOrmQuery; + +public class DefaultLdapOrmQuery extends DefaultOrmQuery { + + private static final long serialVersionUID = -4344629258591773124L; + + public DefaultLdapOrmQuery(Class beanType, EbeanServer server, ExpressionFactory expressionFactory, String query) { + super(beanType, server, expressionFactory, query); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapPersister.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapPersister.java new file mode 100644 index 000000000..843455505 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/DefaultLdapPersister.java @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; + +import com.avaje.ebean.config.ldap.LdapContextFactory; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +public class DefaultLdapPersister { + + private static final Logger logger = Logger.getLogger(DefaultLdapPersister.class.getName()); + + private final LdapContextFactory contextFactory; + + public DefaultLdapPersister(LdapContextFactory dirContextFactory) { + this.contextFactory = dirContextFactory; + } + + public int persist(LdapPersistBeanRequest request) { + + switch (request.getType()) { + case INSERT: + return insert(request); + case UPDATE: + return update(request); + case DELETE: + return delete(request); + + default: + throw new LdapPersistenceException("Invalid type " + request.getType()); + } + } + + private int insert(LdapPersistBeanRequest request) { + + DirContext dc = contextFactory.createContext(); + + Name name = request.createLdapName(); + Attributes attrs = createAttributes(request, false, request.getLoadedProperties()); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("Ldap Insert Name:" + name + " Attrs:" + attrs); + } + try { + dc.bind(name, null, attrs); + return 1; + + } catch (NamingException e) { + throw new LdapPersistenceException(e); + } + } + + private int delete(LdapPersistBeanRequest request) { + + DirContext dc = contextFactory.createContext(); + Name name = request.createLdapName(); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("Ldap Delete Name:" + name); + } + + try { + dc.unbind(name); + return 1; + + } catch (NamingException e) { + throw new LdapPersistenceException(e); + } + } + + private int update(LdapPersistBeanRequest request) { + + Name name = request.createLdapName(); + + Set updatedProperties = request.getUpdatedProperties(); + if (updatedProperties == null || updatedProperties.isEmpty()) { + logger.info("Ldap Update has no changed properties? Name:" + name); + return 0; + } + + DirContext dc = contextFactory.createContext(); + Attributes attrs = createAttributes(request, true, updatedProperties); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("Ldap Update Name:" + name + " Attrs:" + attrs); + } + + try { + dc.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, attrs); + return 1; + + } catch (NamingException e) { + throw new LdapPersistenceException(e); + } + } + + private Attributes createAttributes(LdapPersistBeanRequest request, boolean update, Set props) { + + BeanDescriptor desc = request.getBeanDescriptor(); + + Attributes attrs = desc.createAttributes(); + if (update) { + attrs = new BasicAttributes(true); + } else { + attrs = desc.createAttributes(); + } + + Object bean = request.getBean(); + + if (props != null) { + for (String propName : props) { + BeanProperty p = desc.getBeanPropertyFromPath(propName); + Attribute attr = p.createAttribute(bean); + if (attr != null) { + attrs.put(attr); + } + } + } else { + Iterator it = desc.propertiesAll(); + while (it.hasNext()) { + BeanProperty p = it.next(); + Attribute attr = p.createAttribute(bean); + if (attr != null) { + attrs.put(attr); + } + } + } + + return attrs; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapBeanBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapBeanBuilder.java new file mode 100644 index 000000000..cbd6c1aa1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapBeanBuilder.java @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.logging.Logger; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +public class LdapBeanBuilder { + + private static final Logger logger = Logger.getLogger(LdapBeanBuilder.class.getName()); + + private final BeanDescriptor beanDescriptor; + + private final boolean vanillaMode; + + private Set loadedProps; + + public LdapBeanBuilder(BeanDescriptor beanDescriptor, boolean vanillaMode) { + this.beanDescriptor = beanDescriptor; + this.vanillaMode = vanillaMode; + } + + @SuppressWarnings("unchecked") + public T readAttributes(Attributes attributes) throws NamingException { + + Object bean = beanDescriptor.createBean(vanillaMode); + + NamingEnumeration all = attributes.getAll(); + + boolean setLoadedProps = false; + if (loadedProps == null) { + setLoadedProps = true; + loadedProps = new LinkedHashSet(); + } + + while (all.hasMoreElements()) { + Attribute attr = all.nextElement(); + String attrName = attr.getID(); + + BeanProperty prop = beanDescriptor.getBeanPropertyFromDbColumn(attrName); + if (prop == null) { + if ("objectclass".equalsIgnoreCase(attrName)) { + // this is expected + } else { + logger.info("... hmm, no property to map to attribute[" + attrName + "] value["+attr.get()+"]"); + } + + } else { + prop.setAttributeValue(bean, attr); + if (setLoadedProps) { + loadedProps.add(prop.getName()); + } + } + } + + if (bean instanceof EntityBean) { + EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept(); + ebi.setLoadedProps(loadedProps); + ebi.setLoaded(); + } + + BeanPersistController persistController = beanDescriptor.getPersistController(); + if (persistController != null) { + persistController.postLoad(bean, loadedProps); + } + + return (T)bean; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryEngine.java new file mode 100644 index 000000000..e1ff07733 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryEngine.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import java.util.List; + +import javax.naming.directory.DirContext; + +import com.avaje.ebean.config.ldap.LdapContextFactory; + +public class LdapOrmQueryEngine { + + private final boolean defaultVanillaMode; + + private final LdapContextFactory contextFactory; + + public LdapOrmQueryEngine(boolean defaultVanillaMode, LdapContextFactory contextFactory) { + this.defaultVanillaMode = defaultVanillaMode; + this.contextFactory = contextFactory; + } + + public T findId(LdapOrmQueryRequest request) { + DirContext dc = contextFactory.createContext(); + LdapOrmQueryExecute exe = new LdapOrmQueryExecute(request, defaultVanillaMode, dc); + + return exe.findId(); + } + + public List findList(LdapOrmQueryRequest request) { + + DirContext dc = contextFactory.createContext(); + + LdapOrmQueryExecute exe = new LdapOrmQueryExecute(request, defaultVanillaMode, dc); + + return exe.findList(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryExecute.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryExecute.java new file mode 100644 index 000000000..6ba43def2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryExecute.java @@ -0,0 +1,148 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapName; + +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +public class LdapOrmQueryExecute { + + private static final Logger logger = Logger.getLogger(LdapOrmQueryExecute.class.getName()); + + private final SpiQuery query; + + private final BeanDescriptor beanDescriptor; + + private final DirContext dc; + + private final LdapBeanBuilder beanBuilder; + + private final String filterExpr; + + private final Object[] filterValues; + + private final String[] selectProps; + + public LdapOrmQueryExecute(LdapOrmQueryRequest request, boolean defaultVanillaMode, DirContext dc) { + + this.query = request.getQuery(); + this.beanDescriptor = request.getBeanDescriptor(); + this.dc = dc; + + boolean vanillaMode = query.isVanillaMode(defaultVanillaMode); + this.beanBuilder = new LdapBeanBuilder(beanDescriptor, vanillaMode); + + LdapQueryDeployHelper deployHelper = new LdapQueryDeployHelper(request); + this.selectProps = deployHelper.getSelectedProperties(); + this.filterExpr = deployHelper.getFilterExpr(); + this.filterValues = deployHelper.getFilterValues(); + } + + public T findId() { + + Object id = query.getId(); + + try { + LdapName dn = beanDescriptor.createLdapNameById(id); + + String[] findAttrs = selectProps; + if (findAttrs == null){ + findAttrs = beanDescriptor.getDefaultSelectDbArray(); + } + + // build a string describing the query + String debugQuery = "Name:"+dn+" attrs:"+Arrays.toString(findAttrs); + + Attributes attrs = dc.getAttributes(dn, findAttrs); + + T bean = beanBuilder.readAttributes(attrs); + + query.setGeneratedSql(debugQuery); + return bean; + + } catch (NamingException e) { + throw new LdapPersistenceException(e); + } + } + + public List findList() { + + SearchControls sc = new SearchControls(); + sc.setSearchScope(SearchControls.ONELEVEL_SCOPE); + + List list = new ArrayList(); + + try { + LdapName dn = beanDescriptor.createLdapName(null); + + // build a string describing the query + String debugQuery = "Name:"+dn; + + if (selectProps != null) { + sc.setReturningAttributes(selectProps); + debugQuery += " select:"+Arrays.toString(selectProps); + } + + if (logger.isLoggable(Level.INFO)){ + logger.info("Ldap Query Name:"+dn+" filterExpr:"+filterExpr); + } + + debugQuery += " filterExpr:"+filterExpr; + + NamingEnumeration result; + if (filterValues == null || filterValues.length == 0) { + result = dc.search(dn, filterExpr, sc); + } else { + debugQuery += " filterValues:"+Arrays.toString(filterValues); + result = dc.search(dn, filterExpr, filterValues, sc); + } + + query.setGeneratedSql(debugQuery); + + if (result != null){ + while (result.hasMoreElements()) { + SearchResult row = result.nextElement(); + T bean = beanBuilder.readAttributes(row.getAttributes()); + list.add(bean); + } + } + + return list; + + } catch (NamingException e) { + throw new LdapPersistenceException(e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryRequest.java new file mode 100644 index 000000000..3352d7475 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapOrmQueryRequest.java @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryResultVisitor; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +public class LdapOrmQueryRequest implements SpiOrmQueryRequest { + + private final SpiQuery query; + private final BeanDescriptor desc; + private final LdapOrmQueryEngine queryEngine; + + public LdapOrmQueryRequest(SpiQuery query, BeanDescriptor desc, LdapOrmQueryEngine queryEngine) { + this.query = query; + this.desc = desc; + this.queryEngine = queryEngine; + } + + public BeanDescriptor getBeanDescriptor() { + return desc; + } + + public SpiQuery getQuery() { + return query; + } + + public Object findId() { + return queryEngine.findId(this); + } + + public List findIds() { + throw new RuntimeException("Not Implemented yet"); + } + + public List findList() { + return queryEngine.findList(this); + } + + public void findVisit(QueryResultVisitor visitor) { + throw new RuntimeException("Not Implemented yet"); + } + + public QueryIterator findIterate() { + throw new RuntimeException("Not Implemented yet"); + } + + public Map findMap() { + throw new RuntimeException("Not Implemented yet"); + } + + public int findRowCount() { + throw new RuntimeException("Not Implemented yet"); + } + + public Set findSet() { + throw new RuntimeException("Not Implemented yet"); + } + + public T getFromPersistenceContextOrCache() { + return null; + } + + public BeanCollection getFromQueryCache() { + return null; + } + + public void initTransIfRequired() { + // nothing to do here + } + + public void rollbackTransIfRequired() { + // nothing to do here + } + + public void endTransIfRequired() { + // nothing to do here + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistBeanRequest.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistBeanRequest.java new file mode 100644 index 000000000..b35c663cb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistBeanRequest.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import java.util.Set; + +import javax.naming.ldap.LdapName; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanManager; + +public class LdapPersistBeanRequest extends PersistRequestBean { + + private final DefaultLdapPersister persister; + + public LdapPersistBeanRequest(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, DefaultLdapPersister persister) { + + super(server, bean, parentBean, mgr, null, null); + this.persister = persister; + } + + public LdapPersistBeanRequest(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, DefaultLdapPersister persister, + Set updateProps, ConcurrencyMode concurrencyMode) { + + super(server, bean, parentBean, mgr, null, null, updateProps, concurrencyMode); + this.persister = persister; + } + + public LdapName createLdapName() { + return beanDescriptor.createLdapName(bean); + } + + @Override + public int executeNow() { + + return persister.persist(this); + } + + @Override + public int executeOrQueue() { + return executeNow(); + } + + @Override + public void initTransIfRequired() { + // no transaction at this stage for Ldap + } + + @Override + public void commitTransIfRequired() { + // no transaction at this stage for Ldap + } + + @Override + public void rollbackTransIfRequired() { + // no transaction at this stage for Ldap + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistenceException.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistenceException.java new file mode 100644 index 000000000..4a2358b7e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapPersistenceException.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import javax.persistence.PersistenceException; + +public class LdapPersistenceException extends PersistenceException { + + private static final long serialVersionUID = -3170359404117927668L; + + public LdapPersistenceException(Throwable e){ + super(e); + } + + public LdapPersistenceException(String msg, Throwable e){ + super(msg, e); + } + + public LdapPersistenceException(String msg){ + super(msg); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapQueryDeployHelper.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapQueryDeployHelper.java new file mode 100644 index 000000000..7e9ac9dd7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/LdapQueryDeployHelper.java @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DeployPropertyParser; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; +import com.avaje.ebeaninternal.util.DefaultExpressionRequest; + +public class LdapQueryDeployHelper { + + private final LdapOrmQueryRequest request; + private final SpiQuery query; + private final BeanDescriptor desc; + + private String filterExpr; + private Object[] filterValues; + + public LdapQueryDeployHelper(LdapOrmQueryRequest request) { + this.request = request; + this.query = request.getQuery(); + this.desc = request.getBeanDescriptor(); + + parse(); + } + + public String[] getSelectedProperties() { + + OrmQueryProperties chunk = query.getDetail().getChunk(null, false); + if (chunk.allProperties()) { + return null; + } + + // convert to array of String[] for setReturningAttributes(); + ArrayList ldapSelectProps = new ArrayList(); + + Iterator selectProperties = chunk.getSelectProperties(); + while (selectProperties.hasNext()) { + String propName = selectProperties.next(); + BeanProperty p = desc.getBeanProperty(propName); + if (p != null) { + propName = p.getDbColumn(); + } + ldapSelectProps.add(propName); + } + return ldapSelectProps.toArray(new String[ldapSelectProps.size()]); + + } + + private void parse() { + + DeployPropertyParser deployParser = desc.createDeployPropertyParser(); + + String baseWhere = query.getAdditionalWhere(); + if (baseWhere != null){ + baseWhere = deployParser.parse(baseWhere); + } + + + SpiExpressionList whereExp = query.getWhereExpressions(); + if (whereExp != null) { + + DefaultExpressionRequest expReq = new DefaultExpressionRequest(request, deployParser); + + ArrayList bindValues = whereExp.buildBindValues(expReq); + filterValues = bindValues.toArray(new Object[bindValues.size()]); + String exprWhere = whereExp.buildSql(expReq); + + if (baseWhere != null){ + filterExpr = "(&"+baseWhere +exprWhere+")"; + } else { + filterExpr = exprWhere; + } + } else { + filterExpr = baseWhere; + } + } + + public String getFilterExpr() { + return filterExpr; + } + + public Object[] getFilterValues() { + return filterValues; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdAbstractExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdAbstractExpression.java new file mode 100644 index 000000000..c74e69d0a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdAbstractExpression.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Base class for simple expressions. + * + * @author rbygrave + */ +public abstract class LdAbstractExpression implements SpiExpression { + + private static final long serialVersionUID = 4072786211853856174L; + + protected final String propertyName; + + protected LdAbstractExpression(String propertyName) { + this.propertyName = propertyName; + } + + protected String nextParam(SpiExpressionRequest request) { + + int pos = request.nextParameter(); + return "{"+(pos-1)+"}"; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + if (propertyName != null){ + ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName); + if (elProp != null && elProp.containsMany()){ + manyWhereJoin.add(elProp); + } + } + } + + protected ElPropertyValue getElProp(SpiExpressionRequest request) { + + return request.getBeanDescriptor().getElGetValue(propertyName); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdEscape.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdEscape.java new file mode 100644 index 000000000..167396f72 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdEscape.java @@ -0,0 +1,33 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +public class LdEscape { + + + public static String forLike(String source) { + + // not escaping '*' to "\\\\2a" + + StringBuilder sb = new StringBuilder(source.length()+5); + + int len = source.length(); + for (int i = 0; i < len; i++) { + + char ch = source.charAt(i); + switch (ch) { + case '(': + sb.append("\\\\28");break; + case ')': + sb.append("\\\\29");break; + case '\\': + sb.append("\\\\5c");break; + case '/': + sb.append("\\\\2f");break; + case '\0': + sb.append("\\\\0");break; + default: + sb.append(ch); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdIdExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdIdExpression.java new file mode 100644 index 000000000..6c7d2961f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdIdExpression.java @@ -0,0 +1,70 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.util.DefaultExpressionRequest; + +/** + * Slightly redundant as Query.setId() ultimately also does the same job. + */ +class LdIdExpression extends LdAbstractExpression implements SpiExpression { + + private static final long serialVersionUID = -3065936341718489843L; + + private final Object value; + + LdIdExpression(Object value) { + super(null); + this.value = value; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + } + + public void addBindValues(SpiExpressionRequest request) { + + // 'flatten' EmbeddedId and multiple Id cases + // into an array of the underlying scalar field values + DefaultExpressionRequest r = (DefaultExpressionRequest) request; + BeanProperty[] propertiesId = r.getBeanDescriptor().propertiesId(); + if (propertiesId.length > 1) { + throw new RuntimeException("Only single Id property is supported for LDAP"); + } + request.addBindValue(value); + } + + public void addSql(SpiExpressionRequest request) { + + DefaultExpressionRequest r = (DefaultExpressionRequest) request; + BeanProperty[] propertiesId = r.getBeanDescriptor().propertiesId(); + if (propertiesId.length > 1) { + throw new RuntimeException("Only single Id property is supported for LDAP"); + } + + String ldapProp = propertiesId[0].getDbColumn(); + request.append(ldapProp).append("=").append(nextParam(request)); + } + + /** + * No properties so this is just a unique static number. + */ + public int queryAutoFetchHash() { + // this number is unique for a given bean type + // which is all that is required + return LdIdExpression.class.getName().hashCode(); + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return value.hashCode(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdJunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdJunctionExpression.java new file mode 100644 index 000000000..899d73252 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdJunctionExpression.java @@ -0,0 +1,421 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.Expression; +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.FutureList; +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.Junction; +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.PagingList; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryListener; +import com.avaje.ebean.QueryResultVisitor; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.util.DefaultExpressionList; + +/** + * Junction implementation. + */ +abstract class LdJunctionExpression implements Junction, SpiExpression { + + private static final long serialVersionUID = -7422204102750462677L; + + static class Conjunction extends LdJunctionExpression { + + private static final long serialVersionUID = -645619859900030679L; + + Conjunction(com.avaje.ebean.Query query, ExpressionList parent) { + super("&", query, parent); + } + + Conjunction(ExpressionFactory exprFactory) { + super("&", exprFactory); + } + } + + static class Disjunction extends LdJunctionExpression { + + private static final long serialVersionUID = -8464470066692221414L; + + Disjunction(com.avaje.ebean.Query query, ExpressionList parent) { + super("|", query, parent); + } + + Disjunction(ExpressionFactory exprFactory) { + super("|", exprFactory); + } + } + + private final DefaultExpressionList exprList; + + private final String joinType; + + LdJunctionExpression(String joinType, com.avaje.ebean.Query query, ExpressionList parent) { + this.joinType = joinType; + this.exprList = new DefaultExpressionList(query, parent); + } + + LdJunctionExpression(String joinType, ExpressionFactory exprFactory) { + this.joinType = joinType; + this.exprList = new DefaultExpressionList(null, exprFactory, null); + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + list.get(i).containsMany(desc, manyWhereJoin); + } + } + + public Junction add(Expression item) { + SpiExpression i = (SpiExpression) item; + exprList.add(i); + return this; + } + + public Junction addAll(ExpressionList addList) { + exprList.addAll(addList); + return this; + } + + public void addBindValues(SpiExpressionRequest request) { + + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + SpiExpression item = list.get(i); + item.addBindValues(request); + } + } + + public void addSql(SpiExpressionRequest request) { + + List list = exprList.internalList(); + if (!list.isEmpty()) { + request.append("("); + request.append(joinType); + + for (int i = 0; i < list.size(); i++) { + SpiExpression item = list.get(i); + item.addSql(request); + } + + request.append(") "); + } + } + + /** + * Based on Junction type and all the expression contained. + */ + public int queryAutoFetchHash() { + int hc = LdJunctionExpression.class.getName().hashCode(); + hc = hc * 31 + joinType.hashCode(); + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryAutoFetchHash(); + } + + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + int hc = LdJunctionExpression.class.getName().hashCode(); + hc = hc * 31 + joinType.hashCode(); + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryPlanHash(request); + } + + return hc; + } + + public int queryBindHash() { + int hc = LdJunctionExpression.class.getName().hashCode(); + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + hc = hc * 31 + list.get(i).queryBindHash(); + } + + return hc; + } + + public ExpressionList endJunction() { + return exprList.endJunction(); + } + + public ExpressionList allEq(Map propertyMap) { + return exprList.allEq(propertyMap); + } + + public ExpressionList and(Expression expOne, Expression expTwo) { + return exprList.and(expOne, expTwo); + } + + public ExpressionList between(String propertyName, Object value1, Object value2) { + return exprList.between(propertyName, value1, value2); + } + + public ExpressionList betweenProperties(String lowProperty, String highProperty, Object value) { + return exprList.betweenProperties(lowProperty, highProperty, value); + } + + public Junction conjunction() { + return exprList.conjunction(); + } + + public ExpressionList contains(String propertyName, String value) { + return exprList.contains(propertyName, value); + } + + public Junction disjunction() { + return exprList.disjunction(); + } + + public ExpressionList endsWith(String propertyName, String value) { + return exprList.endsWith(propertyName, value); + } + + public ExpressionList eq(String propertyName, Object value) { + return exprList.eq(propertyName, value); + } + + public ExpressionList exampleLike(Object example) { + return exprList.exampleLike(example); + } + + public ExpressionList filterMany(String prop) { + throw new RuntimeException("filterMany not allowed on Junction expression list"); + } + + public FutureIds findFutureIds() { + return exprList.findFutureIds(); + } + + public FutureList findFutureList() { + return exprList.findFutureList(); + } + + public FutureRowCount findFutureRowCount() { + return exprList.findFutureRowCount(); + } + + public List findIds() { + return exprList.findIds(); + } + + public void findVisit(QueryResultVisitor visitor) { + exprList.findVisit(visitor); + } + + public QueryIterator findIterate() { + return exprList.findIterate(); + } + + public List findList() { + return exprList.findList(); + } + + public Map findMap() { + return exprList.findMap(); + } + + public Map findMap(String keyProperty, Class keyType) { + return exprList.findMap(keyProperty, keyType); + } + + public PagingList findPagingList(int pageSize) { + return exprList.findPagingList(pageSize); + } + + public int findRowCount() { + return exprList.findRowCount(); + } + + public Set findSet() { + return exprList.findSet(); + } + + public T findUnique() { + return exprList.findUnique(); + } + + public ExpressionList ge(String propertyName, Object value) { + return exprList.ge(propertyName, value); + } + + public ExpressionList gt(String propertyName, Object value) { + return exprList.gt(propertyName, value); + } + + public ExpressionList having() { + throw new RuntimeException("having() not allowed on Junction expression list"); + } + + public ExpressionList icontains(String propertyName, String value) { + return exprList.icontains(propertyName, value); + } + + public ExpressionList idEq(Object value) { + return exprList.idEq(value); + } + + public ExpressionList idIn(List idValues) { + return exprList.idIn(idValues); + } + + public ExpressionList iendsWith(String propertyName, String value) { + return exprList.iendsWith(propertyName, value); + } + + public ExpressionList ieq(String propertyName, String value) { + return exprList.ieq(propertyName, value); + } + + public ExpressionList iexampleLike(Object example) { + return exprList.iexampleLike(example); + } + + public ExpressionList ilike(String propertyName, String value) { + return exprList.ilike(propertyName, value); + } + + public ExpressionList in(String propertyName, Collection values) { + return exprList.in(propertyName, values); + } + + public ExpressionList in(String propertyName, Object... values) { + return exprList.in(propertyName, values); + } + + public ExpressionList in(String propertyName, com.avaje.ebean.Query subQuery) { + return exprList.in(propertyName, subQuery); + } + + public ExpressionList isNotNull(String propertyName) { + return exprList.isNotNull(propertyName); + } + + public ExpressionList isNull(String propertyName) { + return exprList.isNull(propertyName); + } + + public ExpressionList istartsWith(String propertyName, String value) { + return exprList.istartsWith(propertyName, value); + } + + public com.avaje.ebean.Query join(String assocProperty, String assocProperties) { + return exprList.join(assocProperty, assocProperties); + } + + public com.avaje.ebean.Query join(String assocProperties) { + return exprList.join(assocProperties); + } + + public ExpressionList le(String propertyName, Object value) { + return exprList.le(propertyName, value); + } + + public ExpressionList like(String propertyName, String value) { + return exprList.like(propertyName, value); + } + + public ExpressionList lt(String propertyName, Object value) { + return exprList.lt(propertyName, value); + } + + public ExpressionList ne(String propertyName, Object value) { + return exprList.ne(propertyName, value); + } + + public ExpressionList not(Expression exp) { + return exprList.not(exp); + } + + public ExpressionList or(Expression expOne, Expression expTwo) { + return exprList.or(expOne, expTwo); + } + + public OrderBy order() { + return exprList.order(); + } + + public com.avaje.ebean.Query order(String orderByClause) { + return exprList.order(orderByClause); + } + + public OrderBy orderBy() { + return exprList.orderBy(); + } + + public com.avaje.ebean.Query orderBy(String orderBy) { + return exprList.orderBy(orderBy); + } + + public com.avaje.ebean.Query query() { + return exprList.query(); + } + + public ExpressionList raw(String raw, Object value) { + return exprList.raw(raw, value); + } + + public ExpressionList raw(String raw, Object[] values) { + return exprList.raw(raw, values); + } + + public ExpressionList raw(String raw) { + return exprList.raw(raw); + } + + public com.avaje.ebean.Query select(String properties) { + return exprList.select(properties); + } + + public com.avaje.ebean.Query setBackgroundFetchAfter(int backgroundFetchAfter) { + return exprList.setBackgroundFetchAfter(backgroundFetchAfter); + } + + public com.avaje.ebean.Query setFirstRow(int firstRow) { + return exprList.setFirstRow(firstRow); + } + + public com.avaje.ebean.Query setListener(QueryListener queryListener) { + return exprList.setListener(queryListener); + } + + public com.avaje.ebean.Query setMapKey(String mapKey) { + return exprList.setMapKey(mapKey); + } + + public com.avaje.ebean.Query setMaxRows(int maxRows) { + return exprList.setMaxRows(maxRows); + } + + public com.avaje.ebean.Query setOrderBy(String orderBy) { + return exprList.setOrderBy(orderBy); + } + + public com.avaje.ebean.Query setUseCache(boolean useCache) { + return exprList.setUseCache(useCache); + } + + public ExpressionList startsWith(String propertyName, String value) { + return exprList.startsWith(propertyName, value); + } + + public ExpressionList where() { + return exprList.where(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLikeExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLikeExpression.java new file mode 100644 index 000000000..9df27402d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLikeExpression.java @@ -0,0 +1,57 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; + +class LdLikeExpression extends LdAbstractExpression { + + private static final long serialVersionUID = 4091359751840929076L; + + private final String value; + + public LdLikeExpression(String propertyName, String value) { + super(propertyName); + this.value = value; + } + + public String getPropertyName() { + return propertyName; + } + + public void addBindValues(SpiExpressionRequest request) { + // Not using binding with wildcards + } + + public void addSql(SpiExpressionRequest request) { + + String escapedValue; + if (value == null) { + escapedValue = "*"; + } else { + escapedValue = LdEscape.forLike(value); + } + + String parsed = request.parseDeploy(propertyName); + + request.append("(").append(parsed).append("=").append(escapedValue).append(")"); + + } + + /** + * Based on the type and propertyName. + */ + public int queryAutoFetchHash() { + int hc = LdLikeExpression.class.getName().hashCode(); + hc = hc * 31 + propertyName.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return value.hashCode(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLogicExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLogicExpression.java new file mode 100644 index 000000000..666174a41 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdLogicExpression.java @@ -0,0 +1,92 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebean.Expression; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * A logical And or Or for joining two expressions. + */ +abstract class LdLogicExpression implements SpiExpression { + + private static final long serialVersionUID = 616860781960645251L; + + static final String AND = "&"; + static final String OR = "|"; + + static class And extends LdLogicExpression { + + private static final long serialVersionUID = -3832889676798526445L; + + And(Expression expOne, Expression expTwo) { + super(AND, expOne, expTwo); + } + } + + static class Or extends LdLogicExpression { + + private static final long serialVersionUID = -6871993143194094810L; + + Or(Expression expOne, Expression expTwo) { + super(OR, expOne, expTwo); + } + } + + private final SpiExpression expOne; + + private final SpiExpression expTwo; + + private final String joinType; + + LdLogicExpression(String joinType, Expression expOne, Expression expTwo) { + this.joinType = joinType; + this.expOne = (SpiExpression) expOne; + this.expTwo = (SpiExpression) expTwo; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + expOne.containsMany(desc, manyWhereJoin); + expTwo.containsMany(desc, manyWhereJoin); + } + + public void addBindValues(SpiExpressionRequest request) { + expOne.addBindValues(request); + expTwo.addBindValues(request); + } + + public void addSql(SpiExpressionRequest request) { + + request.append("("); + request.append(joinType); + expOne.addSql(request); + expTwo.addSql(request); + request.append(") "); + } + + /** + * Based on the joinType plus the two expressions. + */ + public int queryAutoFetchHash() { + int hc = LdLogicExpression.class.getName().hashCode() + joinType.hashCode(); + hc = hc * 31 + expOne.queryAutoFetchHash(); + hc = hc * 31 + expTwo.queryAutoFetchHash(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + int hc = LdLogicExpression.class.getName().hashCode() + joinType.hashCode(); + hc = hc * 31 + expOne.queryPlanHash(request); + hc = hc * 31 + expTwo.queryPlanHash(request); + return hc; + } + + public int queryBindHash() { + int hc = expOne.queryBindHash(); + hc = hc * 31 + expTwo.queryBindHash(); + return hc; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdNotExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdNotExpression.java new file mode 100644 index 000000000..0824348fd --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdNotExpression.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebean.Expression; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +final class LdNotExpression implements SpiExpression { + + private static final long serialVersionUID = 5648926732402355782L; + + private static final String NOT = "!"; + + private final SpiExpression exp; + + LdNotExpression(Expression exp) { + this.exp = (SpiExpression) exp; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + exp.containsMany(desc, manyWhereJoin); + } + + public void addBindValues(SpiExpressionRequest request) { + exp.addBindValues(request); + } + + public void addSql(SpiExpressionRequest request) { + request.append("("); + request.append(NOT); + exp.addSql(request); + request.append(")"); + } + + /** + * Based on the expression. + */ + public int queryAutoFetchHash() { + int hc = LdNotExpression.class.getName().hashCode(); + hc = hc * 31 + exp.queryAutoFetchHash(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + int hc = LdNotExpression.class.getName().hashCode(); + hc = hc * 31 + exp.queryPlanHash(request); + return hc; + } + + public int queryBindHash() { + return exp.queryBindHash(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdPresentExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdPresentExpression.java new file mode 100644 index 000000000..4b9412f22 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdPresentExpression.java @@ -0,0 +1,45 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; + +class LdPresentExpression extends LdAbstractExpression { + + private static final long serialVersionUID = -4221300142054382003L; + + public LdPresentExpression(String propertyName) { + super(propertyName); + } + + public String getPropertyName() { + return propertyName; + } + + public void addBindValues(SpiExpressionRequest request) { + // no bind values + } + + public void addSql(SpiExpressionRequest request) { + + String parsed = request.parseDeploy(propertyName); + request.append("(").append(parsed).append("=*").append(")"); + } + + /** + * Based on the type and propertyName. + */ + public int queryAutoFetchHash() { + int hc = LdPresentExpression.class.getName().hashCode(); + hc = hc * 31 + propertyName.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return 1; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdRawExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdRawExpression.java new file mode 100644 index 000000000..cb60a0e35 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdRawExpression.java @@ -0,0 +1,54 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +class LdRawExpression implements SpiExpression { + + private static final long serialVersionUID = 7973903141340334607L; + + private final String rawExpr; + + private final Object[] values; + + LdRawExpression(String rawExpr, Object[] values) { + this.rawExpr = rawExpr; + this.values = values; + } + + public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { + + } + + public void addBindValues(SpiExpressionRequest request) { + if (values != null) { + for (int i = 0; i < values.length; i++) { + request.addBindValue(values[i]); + } + } + } + + public void addSql(SpiExpressionRequest request) { + request.append(rawExpr); + } + + /** + * Based on the raw expression. + */ + public int queryAutoFetchHash() { + int hc = LdRawExpression.class.getName().hashCode(); + hc = hc * 31 + rawExpr.hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return rawExpr.hashCode(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdSimpleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdSimpleExpression.java new file mode 100644 index 000000000..a84a68735 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdSimpleExpression.java @@ -0,0 +1,113 @@ +package com.avaje.ebeaninternal.server.ldap.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.type.ScalarType; + +class LdSimpleExpression extends LdAbstractExpression { + + private static final long serialVersionUID = 4091359751840929075L; + + enum Op { + EQ { + public String toString() { + return "="; + } + }, + NOT_EQ { + public String toString() { + return "<>"; + } + }, + LT { + public String toString() { + return "<"; + } + }, + LT_EQ { + public String toString() { + return "<="; + } + }, + GT { + public String toString() { + return ">"; + } + }, + GT_EQ { + public String toString() { + return ">="; + } + } + } + + private final Op type; + + private final Object value; + + public LdSimpleExpression(String propertyName, Op type, Object value) { + super(propertyName); + this.type = type; + this.value = value; + } + + public String getPropertyName() { + return propertyName; + } + + public void addBindValues(SpiExpressionRequest request) { + + ElPropertyValue prop = getElProp(request); + if (prop != null) { + if (prop.isAssocId()) { + Object[] ids = prop.getAssocOneIdValues(value); + if (ids != null) { + for (int i = 0; i < ids.length; i++) { + request.addBindValue(ids[i]); + } + } + return; + } + ScalarType scalarType = prop.getBeanProperty().getScalarType(); + Object v = scalarType.toJdbcType(value); + request.addBindValue(v); + } else { + request.addBindValue(value); + } + } + + public void addSql(SpiExpressionRequest request) { + ElPropertyValue prop = getElProp(request); + if (prop != null) { + if (prop.isAssocId()) { + String rawExpr = prop.getAssocOneIdExpr(propertyName, type.toString()); + String parsed = request.parseDeploy(rawExpr); + request.append(parsed); + return; + } + } + String parsed = request.parseDeploy(propertyName); + + request.append("(").append(parsed).append("").append(type.toString()).append(nextParam(request)).append(")"); + } + + /** + * Based on the type and propertyName. + */ + public int queryAutoFetchHash() { + int hc = LdSimpleExpression.class.getName().hashCode(); + hc = hc * 31 + propertyName.hashCode(); + hc = hc * 31 + type.name().hashCode(); + return hc; + } + + public int queryPlanHash(BeanQueryRequest request) { + return queryAutoFetchHash(); + } + + public int queryBindHash() { + return value.hashCode(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdapExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdapExpressionFactory.java new file mode 100644 index 000000000..5dbf04bb0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/LdapExpressionFactory.java @@ -0,0 +1,268 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.ldap.expression; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.avaje.ebean.ExampleExpression; +import com.avaje.ebean.Expression; +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.Junction; +import com.avaje.ebean.LikeType; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.server.ldap.LdapPersistenceException; +import com.avaje.ebeaninternal.server.ldap.expression.LdSimpleExpression.Op; + +public class LdapExpressionFactory implements ExpressionFactory { + + public String getLang() { + return "ldap"; + } + + public ExpressionFactory createExpressionFactory(String path) { + return new LdapExpressionFactory(); + } + + @SuppressWarnings({ "rawtypes" }) + public Expression allEq(Map propertyMap) { + + Junction conjunction = new LdJunctionExpression.Conjunction(this); + + Iterator> it = propertyMap.entrySet().iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + conjunction.add(eq(entry.getKey(), entry.getValue())); + } + return conjunction; + } + + public Expression and(Expression expOne, Expression expTwo) { + return new LdLogicExpression.And(expOne, expTwo); + } + + public Expression between(String propertyName, Object value1, Object value2) { + Expression e1 = gt(propertyName, value1); + Expression e2 = lt(propertyName, value2); + return and(e1, e2); + } + + public Expression betweenProperties(String lowProperty, String highProperty, Object value) { + throw new RuntimeException("Not Implemented"); + } + + public Expression contains(String propertyName, String value) { + if (!value.endsWith("*")){ + value = "*"+value+"*"; + } + return new LdSimpleExpression(propertyName, Op.EQ, value); + } + + public Junction conjunction(Query query) { + return new LdJunctionExpression.Conjunction(query, query.where()); + } + + + public Junction disjunction(Query query) { + return new LdJunctionExpression.Disjunction(query, query.where()); + } + + public Junction conjunction(Query query, ExpressionList parent) { + return new LdJunctionExpression.Conjunction(query, parent); + } + + public Junction disjunction(Query query, ExpressionList parent) { + return new LdJunctionExpression.Disjunction(query, parent); + } + + public Expression endsWith(String propertyName, String value) { + if (!value.startsWith("*")){ + value = "*"+value; + } + return new LdLikeExpression(propertyName, value); + } + + public Expression eq(String propertyName, Object value) { + return new LdSimpleExpression(propertyName, Op.EQ, value); + } + + public Expression lucene(String propertyName, String value) { + throw new RuntimeException("Not Implemented"); + } + + public Expression lucene(String value) { + throw new RuntimeException("Not Implemented"); + } + + public ExampleExpression exampleLike(Object example, boolean caseInsensitive, LikeType likeType) { + throw new RuntimeException("Not Implemented"); + } + + public ExampleExpression exampleLike(Object example) { + throw new RuntimeException("Not Implemented"); + } + + public Expression ge(String propertyName, Object value) { + return new LdSimpleExpression(propertyName, Op.GT_EQ, value); + } + + public Expression gt(String propertyName, Object value) { + return new LdSimpleExpression(propertyName, Op.GT, value); + } + + public Expression icontains(String propertyName, String value) { + if (!value.endsWith("*")){ + value = "*"+value+"*"; + } + return new LdLikeExpression(propertyName, value); + } + + public Expression idEq(Object value) { + // TODO Auto-generated method stub + return null; + } + + public Expression idIn(List idList) { + throw new RuntimeException("Not Implemented"); + } + + public Expression iendsWith(String propertyName, String value) { + if (!value.startsWith("*")){ + value = "*"+value; + } + return new LdLikeExpression(propertyName, value); + } + + public Expression ieq(String propertyName, String value) { + return new LdSimpleExpression(propertyName, Op.EQ, value); + } + + public ExampleExpression iexampleLike(Object example) { + throw new RuntimeException("Not Implemented"); + } + + public Expression ilike(String propertyName, String value) { + return new LdLikeExpression(propertyName, value); + } + + @SuppressWarnings({ "rawtypes" }) + public Expression in(String propertyName, Collection values) { + + if (values == null || values.isEmpty()){ + throw new LdapPersistenceException("collection can't be empty for Ldap"); + } + + Junction disjunction = new LdJunctionExpression.Disjunction(this); + for (Object v : values) { + disjunction.add(eq(propertyName, v)); + } + + return disjunction; + } + + @SuppressWarnings("rawtypes") + public Expression in(String propertyName, Object[] values) { + + if (values == null || values.length == 0){ + throw new LdapPersistenceException("values can't be empty for Ldap"); + } + + Junction disjunction = new LdJunctionExpression.Disjunction(this); + for (Object v : values) { + disjunction.add(eq(propertyName, v)); + } + + return disjunction; + } + + public Expression in(String propertyName, Query subQuery) { + throw new RuntimeException("Not Implemented"); + } + + public Expression isNotNull(String propertyName) { + return new LdPresentExpression(propertyName); + } + + public Expression isNull(String propertyName) { + LdPresentExpression exp = new LdPresentExpression(propertyName); + return new LdNotExpression(exp); + } + + public Expression istartsWith(String propertyName, String value) { + if (!value.endsWith("*")){ + value = value+"*"; + } + return new LdLikeExpression(propertyName, value); + } + + public Expression le(String propertyName, Object value) { + return new LdSimpleExpression(propertyName, Op.LT_EQ, value); + } + + public Expression like(String propertyName, String value) { + return new LdLikeExpression(propertyName, value); + } + + public Expression lt(String propertyName, Object value) { + return new LdSimpleExpression(propertyName, Op.LT, value); + } + + public Expression ne(String propertyName, Object value) { + return new LdSimpleExpression(propertyName, Op.NOT_EQ, value); + } + + public Expression not(Expression exp) { + return new LdNotExpression(exp); + } + + public Expression or(Expression expOne, Expression expTwo) { + return new LdLogicExpression.Or(expOne, expTwo); + } + + public Expression raw(String raw, Object value) { + if (value != null){ + return new LdRawExpression(raw, new Object[]{value}); + + } else { + return new LdRawExpression(raw, null); + } + } + + public Expression raw(String raw, Object[] values) { + return new LdRawExpression(raw, values); + } + + public Expression raw(String raw) { + return new LdRawExpression(raw, null); + } + + public Expression startsWith(String propertyName, String value) { + if (!value.endsWith("*")){ + value = value+"*"; + } + return new LdLikeExpression(propertyName, value); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/package-info.java new file mode 100644 index 000000000..bbc12426f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/expression/package-info.java @@ -0,0 +1,4 @@ +/** + * LDAP query expressions. + */ +package com.avaje.ebeaninternal.server.ldap.expression; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/ldap/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/ldap/package-info.java new file mode 100644 index 000000000..9388fe56a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ldap/package-info.java @@ -0,0 +1,4 @@ +/** + * LDAP query and persist implementation. + */ +package com.avaje.ebeaninternal.server.ldap; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundRunnable.java b/src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundRunnable.java new file mode 100644 index 000000000..c59287589 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundRunnable.java @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib; + +/** + * Wraps a Runnable that is registed with BackgroundThread. + * @see BackgroundThread + */ +public class BackgroundRunnable { + + /** + * The task to run. + */ + Runnable runnable; + + /** + * The frequency to run the task. + */ + int freqInSecs; + + /** + * The number of times the task has run. + */ + int runCount = 0; + + /** + * The total time taken to run the task. + */ + long totalRunTime = 0; + + /** + * The start time the task was started. + */ + long startTimeTemp; + + long startAfter; + + /** + * Used to disable/enable a task. + */ + boolean isActive = true; + + public BackgroundRunnable(Runnable runnable, int freqInSecs){ + this(runnable, freqInSecs, System.currentTimeMillis()+1000*(freqInSecs+10)); + } + + public BackgroundRunnable(Runnable runnable, int freqInSecs, long startAfter){ + this.runnable = runnable; + this.freqInSecs = freqInSecs; + this.startAfter = startAfter; + } + + /** + * Return true if this can be run now. + *

+ * This is used to stop jobs firing immediately. + *

+ */ + public boolean runNow(long now){ + return now > startAfter; + } + + /** + * Returns true if the task is currently enabled. + */ + public boolean isActive() { + return isActive; + } + + /** + * Set this to false to stop this task from running. + * Useful to temporarily disable a particular task. + */ + public void setActive(boolean isActive) { + this.isActive = isActive; + } + + /** + * Mark the start time of a task run. + */ + protected void runStart() { + startTimeTemp = System.currentTimeMillis(); + } + + /** + * Mark the end time of a task run. + */ + protected void runEnd(){ + runCount++; + long exeTime = System.currentTimeMillis() - startTimeTemp; + totalRunTime = totalRunTime + exeTime; + } + + /** + * Return the number of times this task was run. + */ + public int getRunCount() { + return runCount; + } + + /** + * Return the average time this task takes to run. + */ + public long getAverageRunTime() { + if (runCount == 0){ + return 0; + } + return totalRunTime/runCount; + } + + /** + * Return the frequency in seconds that this task runs. + */ + public int getFreqInSecs() { + return freqInSecs; + } + + /** + * Set the frequency in seconds that this task runs. + */ + public void setFreqInSecs(int freqInSecs) { + this.freqInSecs = freqInSecs; + } + + /** + * Return the underlying runnable. + */ + public Runnable getRunnable() { + return runnable; + } + + /** + * Set the underlying runnable. + */ + public void setRunnable(Runnable runnable) { + this.runnable = runnable; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("["); + sb.append(runnable.getClass().getName()); + sb.append(" freq:").append(freqInSecs); + sb.append(" count:").append(getRunCount()); + sb.append(" avgTime:").append(getAverageRunTime()); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundThread.java b/src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundThread.java new file mode 100644 index 000000000..07283757b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/BackgroundThread.java @@ -0,0 +1,236 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib; + +import java.util.Iterator; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A general background thread that runs registered tasks periodically. + *

+ * Several features such as CacheManager, DataSourceManager, ThreadPoolManager + * require tasks to be undertaken periodically. Instead of each having their own + * background thread they register runnables with this one. + *

+ *

+ * SystemProperties:
+ * + *


+ *   ## initially sleep for 5 seconds before starting 
+ *   backgroundthread.initialsleep=5
+ * 
+ * + *

+ * + * @see BackgroundRunnable + */ +public final class BackgroundThread { + + private static final Logger logger = Logger.getLogger(BackgroundThread.class.getName()); + + private static final BackgroundThread me = new BackgroundThread(); + + /** + * The list of Runnable tasks. + */ + private Vector list = new Vector(); + + /** + * Used to synchronize the list. + */ + private final Object monitor = new Object(); + + /** + * The underlying background thread. + */ + private final Thread thread; + + /** + * Wakes every second to look for tasks to run. + */ + private long sleepTime = 1000; + + /** + * The number of times a task is run. + */ + private long count; + + /** + * The time it takes to run the tasks. + */ + private long exeTime; + + /** + * Set when shutting down. + */ + private boolean stopped; + + /** + * Used to shutdown nicely. + */ + private Object threadMonitor = new Object(); + + private BackgroundThread() { + + thread = new Thread(new Runner(), "EbeanBackgroundThread"); + thread.setDaemon(true); + thread.start(); + } + + /** + * Register a Runnable to execute every freqInSecs seconds. + */ + public static void add(int freqInSecs, Runnable runnable) { + add(new BackgroundRunnable(runnable, freqInSecs)); + } + + /** + * Register a Runnable to execute every freqInSecs seconds. + */ + public static void add(BackgroundRunnable backgroundRunnable) { + me.addTask(backgroundRunnable); + } + + /** + * Stop the service. + */ + public static void shutdown() { + me.stop(); + } + + /** + * Return the registered BackgroundRunnable objects. + */ + public static Iterator runnables() { + synchronized (me.monitor) { + return me.list.iterator(); + } + } + + private void addTask(BackgroundRunnable backgroundRunnable) { + synchronized (monitor) { + list.add(backgroundRunnable); + } + } + + /** + * Stop the thread nicely. This will wait a maximum of 10 seconds for + * current work to be finished. + */ + private void stop() { + + stopped = true; + synchronized (threadMonitor) { + try { + threadMonitor.wait(10000); + } catch (InterruptedException e) { + ; + } + } + // thread = null; + } + + private class Runner implements Runnable { + + /** + * Run the registered tasks periodically. + */ + public void run() { + + if (ShutdownManager.isStopping()) { + return; + } + + while (!stopped) { + try { + + long actualSleep = sleepTime - exeTime; + if (actualSleep < 0) { + actualSleep = sleepTime; + } + Thread.sleep(actualSleep); + synchronized (monitor) { + runJobs(); + } + + } catch (InterruptedException e) { + logger.log(Level.SEVERE, null, e); + } + } + + // Tell Stop() we have shut ourselves down successfully + synchronized (threadMonitor) { + threadMonitor.notifyAll(); + } + } + + private void runJobs() { + + long startTime = System.currentTimeMillis(); + + // call trim on each cache + Iterator it = list.iterator(); + while (it.hasNext()) { + BackgroundRunnable bgr = (BackgroundRunnable) it.next(); + if (bgr.isActive()) { + + int freqInSecs = bgr.getFreqInSecs(); + + if (count % freqInSecs == 0) { + Runnable runable = bgr.getRunnable(); + if (bgr.runNow(startTime)){ + bgr.runStart(); + if (logger.isLoggable(Level.FINER)) { + String msg = count + " BGRunnable running [" + + runable.getClass().getName() + "]"; + logger.finer(msg); + } + + runable.run(); + bgr.runEnd(); + } + } + } + } + exeTime = System.currentTimeMillis() - startTime; + count++; + + if (count == 86400) { + // reset count back to zero every day + count = 0; + } + } + } + + public String toString() { + synchronized (monitor) { + StringBuffer sb = new StringBuffer(); + + Iterator it = runnables(); + while (it.hasNext()) { + BackgroundRunnable bgr = it.next(); + sb.append(bgr); + } + + return sb.toString(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java new file mode 100644 index 000000000..52b426420 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib; + + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.api.Monitor; + +/** + * Daemon based ScheduleThreadPool. + *

+ * Uses Daemon threads and hooks into shutdown event. + *

+ * + * @author rbygrave + */ +public final class DaemonScheduleThreadPool extends ScheduledThreadPoolExecutor { + + private static final Logger logger = Logger.getLogger(DaemonScheduleThreadPool.class.getName()); + + private final Monitor monitor = new Monitor(); + + private int shutdownWaitSeconds; + + /** + * Construct the DaemonScheduleThreadPool. + */ + public DaemonScheduleThreadPool(int coreSize, int shutdownWaitSeconds, String namePrefix) { + super(coreSize, new DaemonThreadFactory(namePrefix)); + this.shutdownWaitSeconds = shutdownWaitSeconds; + + // we want to shutdown nicely when either the web application stops. + // Adding the JVM shutdown hook as a safety (and when not run in tomcat) + Runtime.getRuntime().addShutdownHook(new ShutdownHook()); + } + + /** + * Shutdown this thread pool nicely if possible. + *

+ * This will wait a maximum of 20 seconds before terminating any threads + * still working. + *

+ */ + public void shutdown() { + synchronized (monitor) { + if (super.isShutdown()) { + logger.fine("... DaemonScheduleThreadPool already shut down"); + return; + } + try { + logger.fine("DaemonScheduleThreadPool shutting down..."); + super.shutdown(); + if (!super.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) { + logger.info("ScheduleService shut down timeout exceeded. Terminating running threads."); + super.shutdownNow(); + } + + } catch (Exception e) { + String msg = "Error during shutdown"; + logger.log(Level.SEVERE, msg, e); + e.printStackTrace(); + } + } + } + + /** + * Fired by the JVM Runtime shutdown. + */ + private class ShutdownHook extends Thread { + @Override + public void run() { + shutdown(); + } + }; +} + diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java new file mode 100644 index 000000000..5f2684b32 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib; + + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * ThreadFactory for Daemon threads. + *

+ * Daemon threads do not stop a JVM stopping. If an application only has Daemon + * threads left it will shutdown. + *

+ *

+ * In using Daemon threads you need to either not care about being interrupted + * on shutdown or register with the JVM shutdown hook to perform a nice shutdown + * of the daemon threads etc. + *

+ * + * @author rbygrave + */ +public class DaemonThreadFactory implements ThreadFactory { + + private static final AtomicInteger poolNumber = new AtomicInteger(1); + + private final ThreadGroup group; + + private final AtomicInteger threadNumber = new AtomicInteger(1); + + private final String namePrefix; + + public DaemonThreadFactory(String namePrefix) { + SecurityManager s = System.getSecurityManager(); + this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + this.namePrefix = namePrefix != null ? namePrefix : "pool-" + poolNumber.getAndIncrement() + "-thread-"; + } + + public Thread newThread(Runnable r) { + + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + + t.setDaemon(true); + + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + + return t; + } +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java new file mode 100644 index 000000000..cd6cf8c6f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib; + + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.api.Monitor; + +/** + * The Thread Pool based on Daemon threads. + * + * @author rbygrave + */ +public final class DaemonThreadPool extends ThreadPoolExecutor { + + private static final Logger logger = Logger.getLogger(DaemonThreadPool.class.getName()); + + private final Monitor monitor = new Monitor(); + + private final String namePrefix; + + private int shutdownWaitSeconds; + + /** + * Construct the DaemonThreadPool. + * + * @param coreSize + * the core size of the thread pool. + * @param keepAliveSecs + * the time in seconds idle threads are keep alive + * @param shutdownWaitSeconds + * the time in seconds allowed for the pool to shutdown nicely. + * After this the pool is forced to shutdown. + */ + public DaemonThreadPool(int coreSize, long keepAliveSecs, int shutdownWaitSeconds, String namePrefix) { + super(coreSize, coreSize, keepAliveSecs, TimeUnit.SECONDS, new LinkedBlockingQueue(), new DaemonThreadFactory(namePrefix)); + this.shutdownWaitSeconds = shutdownWaitSeconds; + this.namePrefix = namePrefix; + // we want to shutdown nicely when either the web application stops. + // Adding the JVM shutdown hook as a safety (and when not run in tomcat) + Runtime.getRuntime().addShutdownHook(new ShutdownHook()); + } + + /** + * Shutdown this thread pool nicely if possible. + *

+ * This will wait a maximum of 20 seconds before terminating any threads + * still working. + *

+ */ + public void shutdown() { + synchronized (monitor) { + if (super.isShutdown()) { + logger.fine("... DaemonThreadPool["+namePrefix+"] already shut down"); + return; + } + try { + logger.fine("DaemonThreadPool["+namePrefix+"] shutting down..."); + super.shutdown(); + if (!super.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) { + logger.info("DaemonThreadPool["+namePrefix+"] shut down timeout exceeded. Terminating running threads."); + super.shutdownNow(); + } + + } catch (Exception e) { + String msg = "Error during shutdown of DaemonThreadPool["+namePrefix+"]"; + logger.log(Level.SEVERE, msg, e); + e.printStackTrace(); + } + } + } + + /** + * Fired by the JVM Runtime shutdown. + */ + private class ShutdownHook extends Thread { + @Override + public void run() { + shutdown(); + } + }; +} + diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java new file mode 100644 index 000000000..b55f7ea32 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib; + + + +/** + * This is the ShutdownHook that gets added to Runtime. + * It will try to shutdown the system cleanly when the JVM exits. + * It is best to add your own shutdown hooks to StartStop. + * + */ +class ShutdownHook extends Thread { + + ShutdownHook() { + } + +// /** +// * Register this as a Shutdown hook with the Runtime. +// */ +// public static void registerWithRuntime() { +// +// ShutdownHook hook = new ShutdownHook(); +// Runtime.getRuntime().addShutdownHook(hook); +// } + + + /** + * Fired by the JVM Runtime on shutdown. + */ + public void run() { + ShutdownManager.shutdown(); + } + +}; diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java new file mode 100644 index 000000000..e9824a512 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java @@ -0,0 +1,232 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Enumeration; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.common.BootupEbeanManager; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.server.lib.sql.DataSourceGlobalManager; +import com.avaje.ebeaninternal.server.lib.thread.ThreadPoolManager; + +/** + * Manages the shutdown of the Runtime. + *

+ * Makes sure all the resources are shutdown properly and in order. + *

+ */ +public final class ShutdownManager { + + private static final Logger logger = Logger.getLogger(BackgroundThread.class.getName()); + + static final Vector runnables = new Vector(); + + static boolean stopping; + + static BootupEbeanManager serverFactory; + + static final ShutdownHook shutdownHook = new ShutdownHook(); + + static boolean whyShutdown; + + static { + // Register the Shutdown hook + register(); + whyShutdown = GlobalProperties.getBoolean("debug.shutdown.why",false); + } + + /** + * Disallow construction. + */ + private ShutdownManager() { + } + + public static void registerServerFactory(BootupEbeanManager factory){ + serverFactory = factory; + } + /** + * Make sure the ShutdownManager is activated. + */ + public static void touch() { + + } + + /** + * Return true if the system is in the process of stopping. + */ + public static boolean isStopping() { + synchronized (runnables) { + return stopping; + } + } + + /** + * Deregister the Shutdown hook. + *

+ * For running in a Servlet Container a redeploy will cause a shutdown, and + * for that case we need to make sure the shutdown hook is deregistered. + *

+ */ + private static void deregister() { + synchronized (runnables) { + try { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + } catch (IllegalStateException ex) { + if (!ex.getMessage().equals("Shutdown in progress")) { + throw ex; + } + } + } + } + + /** + * Register the shutdown hook with the Runtime. + */ + private static void register() { + synchronized (runnables) { + try { + Runtime.getRuntime().addShutdownHook(shutdownHook); + } catch (IllegalStateException ex) { + if (!ex.getMessage().equals("Shutdown in progress")) { + throw ex; + } + } + } + } + + /** + * cleanup any resources as Runtime is stopping. + *

+ *

    + *
  • Run the application specific shutdown runnable + *
  • Run any other registered shutdown runnable + *
  • Deregister from the cluster if required + *
  • Shutdown Thread pools + *
  • Shutdown any database connection pools + *
+ *

+ */ + public static void shutdown() { + synchronized (runnables) { + if (stopping) { + // Already run shutdown... + return; + } + + if (whyShutdown){ + try { + throw new RuntimeException("debug.shutdown.why=true ..."); + } catch(Throwable e){ + logger.log(Level.WARNING, "Stacktrace showing why shutdown was fired", e); + } + } + + stopping = true; + //logger.info("Stopping [" + SystemProperties.getContextName() + "]"); + + deregister(); + + // stop the BackgroundThread + BackgroundThread.shutdown(); + + String shutdownRunner = GlobalProperties.get("system.shutdown.runnable", null); + if (shutdownRunner != null) { + try { + Runnable r = (Runnable)ClassUtil.newInstance(shutdownRunner); + r.run(); + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + } + } + + // shutdown any registered runnable + + Enumeration e = runnables.elements(); + while (e.hasMoreElements()) { + try { + Runnable r = (Runnable) e.nextElement(); + r.run(); + } catch (Exception ex) { + logger.log(Level.SEVERE, null, ex); + ex.printStackTrace(); + } + } + try { + // shutdown order is important! + // CronManager is ok + + if (serverFactory != null){ + serverFactory.shutdown(); + } + + ThreadPoolManager.shutdown(); + + DataSourceGlobalManager.shutdown(); + + boolean dereg = GlobalProperties.getBoolean("datasource.deregisterAllDrivers", false); + if (dereg){ + deregisterAllJdbcDrivers(); + } + + } catch (Exception ex) { + String msg = "Shutdown Exception: "+ ex.getMessage(); + System.err.println(msg); + ex.printStackTrace(); + try { + logger.log(Level.SEVERE, null, ex); + } catch (Exception exc) { + String ms = "Error Logging error to the Log. It may be shutting down."; + System.err.println(ms); + exc.printStackTrace(); + } + } + } + } + + private static void deregisterAllJdbcDrivers() { + // This manually deregisters JDBC driver, which prevents Tomcat 7 from complaining about memory leaks wrto this class + Enumeration drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) { + Driver driver = drivers.nextElement(); + try { + DriverManager.deregisterDriver(driver); + logger.log(Level.INFO, String.format("Deregistering jdbc driver: %s", driver)); + } catch (SQLException e) { + logger.log(Level.SEVERE, String.format("Error deregistering driver %s", driver), e); + } + } + } + + /** + * Register a runnable to be executed when the system is shutdown. Note that + * runnables registered here are shutdown before any thread pools or + * DataSource pools are shutdown. + */ + public static void register(Runnable runnable) { + synchronized (runnables) { + runnables.add(runnable); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/package.html b/src/main/java/com/avaje/ebeaninternal/server/lib/package.html new file mode 100644 index 000000000..9055f2795 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/package.html @@ -0,0 +1,13 @@ + + + + AvajeLib + + +Core services for an application. +

+Core services including Logging, Deployment properties and Background thread for +running frequent tasks. +

+ + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/AbstractResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/AbstractResourceSource.java new file mode 100644 index 000000000..9fdfeeac9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/AbstractResourceSource.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; + +public abstract class AbstractResourceSource { + + + /** + * Helper method to read String content. + */ + public String readString(ResourceContent content, int bufSize) throws IOException { + + if (content == null) { + throw new NullPointerException("content is null?"); + } + StringWriter writer = new StringWriter(); + + InputStream is = content.getInputStream(); + InputStreamReader reader = new InputStreamReader(is); + + char[] buf = new char[bufSize]; + int len; + while ((len = reader.read(buf, 0, buf.length)) != -1) { + writer.write(buf, 0, len); + } + + reader.close(); + + return writer.toString(); + } + + /** + * Helper method to read byte[] content. + */ + public byte[] readBytes(ResourceContent content, int bufSize) throws IOException { + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + InputStream is = content.getInputStream(); + + byte[] buf = new byte[bufSize]; + int len; + while ((len = is.read(buf, 0, buf.length)) != -1) { + bytes.write(buf, 0, len); + } + + is.close(); + + return bytes.toByteArray(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java new file mode 100644 index 000000000..dc2aa92ea --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Helper object used to find directories typically from the current working + * directory. + */ +public class DirectoryFinder { + + private static final Logger logger = Logger.getLogger(DirectoryFinder.class.getName()); + + /** + * Find a directory by search through subdirectories. + *

+ * For example, used to find the WEB-INF directory starting from the current + * working directory. + *

+ * + *
+	 * 
+	 * // search to a depth of 3 from the current working directory
+	 * // looking for a directory WEB-INF that contains the subdirectory
+	 * // data
+	 * 
+	 * File dir = DirectoryFinder.find(null, "WEB-INF/data", 3);
+	 * if (dir != null) {
+	 * 	//found the directory
+	 * }
+	 * 
+ */ + public static File find(File startDir, String match, int maxDepth) { + + String matchSub = null; + int slashPos = match.indexOf('/'); + if (slashPos > -1) { + // match has sub directories + matchSub = match.substring(slashPos + 1); + match = match.substring(0, slashPos); + } + + // search for the directory + File found = find(startDir, match, matchSub, 0, maxDepth); + + if (found != null && matchSub != null) { + // match has sub directories + return new File(found, matchSub); + } + return found; + } + + private static File find(File dir, String match, String matchSub, int depth, int maxDepth) { + + if (dir == null) { + String curDir = System.getProperty("user.dir"); + dir = new File(curDir); + } + + if (dir.exists()) { + File[] list = dir.listFiles(); + if (list != null){ + for (int i = 0; i < list.length; i++) { + if (isMatch(list[i], match, matchSub)) { + return list[i]; + } + } + + // go through the directories again + // Aka *NOT* a depth first search + if (depth < maxDepth) { + for (int i = 0; i < list.length; i++) { + if (list[i].isDirectory()) { + File found = find(list[i], match, matchSub, depth + 1, maxDepth); + if (found != null) { + return found; + } + } + } + } + } + } + return null; + } + + private static boolean isMatch(File f, String match, String matchSub) { + if (f == null) { + return false; + } + if (!f.isDirectory()) { + return false; + } + if (!f.getName().equalsIgnoreCase(match)) { + return false; + } + if (matchSub == null) { + return true; + } + File sub = new File(f, matchSub); + if (logger.isLoggable(Level.FINEST)){ + logger.finest("search; " + f.getPath()); + } + return sub.exists(); + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java new file mode 100644 index 000000000..ad8554684 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +/** + * Content from a file system file. + */ +public class FileResourceContent implements ResourceContent { + + /** + * The underlying file. + */ + File file; + + String entryName; + + /** + * Create with a File and the entryName. + */ + public FileResourceContent(File file, String entryName) { + this.file = file; + this.entryName = entryName; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("[").append(getName()); + sb.append("] size[").append(size()); + sb.append("] lastModified[").append(new Date(lastModified())); + sb.append("]"); + return sb.toString(); + } + + /** + * Returns the entry name which contains the path from the base directory. + *

+ * This does not return the full path of the file, but the path relative to + * the FileIoSource directory. + *

+ */ + public String getName() { + return entryName; + } + + /** + * Return the time the file was last modified. + */ + public long lastModified() { + return file.lastModified(); + } + + /** + * Return the size of the file. + */ + public long size() { + return file.length(); + } + + /** + * Return the input stream for this file. + */ + public InputStream getInputStream() throws IOException { + + FileInputStream is = new FileInputStream(file); + return is; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java new file mode 100644 index 000000000..963936e38 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.io.File; + +/** + * A file system directory represented as a FileSource. + */ +public class FileResourceSource extends AbstractResourceSource implements ResourceSource { + + /** + * The directory name. + */ + String directory; + + String baseDir; + + /** + * Create the source based on a directory name. + */ + public FileResourceSource(String directory){ + this.directory = directory; + this.baseDir = directory+File.separator; + } + + /** + * Create the source based on a directory file. + */ + public FileResourceSource(File dir){ + this(dir.getPath()); + } + + + public String getRealPath() { + return directory; + } + + /** + * Search for the given file and return as IoContent. + */ + public ResourceContent getContent(String entry) { + + String fullPath = baseDir+entry; + + File f = new File(fullPath); + if (f.exists()){ + FileResourceContent content = new FileResourceContent(f, entry); + return content; + } + return null; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java new file mode 100644 index 000000000..8343ff3b5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Represents content that can be read via the ResourceManager. + *

+ * Typically either content from a File or a URL. + *

+ */ +public interface ResourceContent { + + /** + * The name of the content. + */ + public String getName(); + + /** + * The size of the content in bytes. + */ + public long size(); + + /** + * The last modified timestamp of the content. + */ + public long lastModified(); + + /** + * The content itself. + */ + public InputStream getInputStream() throws IOException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java new file mode 100644 index 000000000..5630270a4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.io.IOException; + +/** + * A Source for ResourceManager. + *

+ * Typically a File System Directory based source or a ServletContext URL + * resource based source (for Servlet WAR files). + *

+ */ +public interface ResourceSource { + + /** + * Return the File System path of the root of the ResourceSource. + *

+ * This will return null IF the ResourceSource is an unpacked WAR file. + *

+ */ + public String getRealPath(); + + /** + * Find the content with a given entry name. This will return null if no + * matching content was found. + */ + public ResourceContent getContent(String entry); + + /** + * Return the content as a String. + */ + public String readString(ResourceContent content, int bufSize) throws IOException; + + /** + * Return the content as a byte[]. + */ + public byte[] readBytes(ResourceContent content, int bufSize) throws IOException; +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java new file mode 100644 index 000000000..ad23d32ad --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; + +/** + * Content from a URL Resource. + */ +public class UrlResourceContent implements ResourceContent { + + /** + * The underlying resource. + */ + //URL url; + + String entryName; + + URLConnection con; + + /** + * Create with a File and the entryName. + */ + public UrlResourceContent(URL url, String entryName) { + //this.url = url; + this.entryName = entryName; + try { + con = url.openConnection(); + } catch (IOException ex){ + throw new RuntimeException(ex); + } + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("[").append(getName()); + sb.append("] size[").append(size()); + sb.append("] lastModified[").append(new Date(lastModified())); + sb.append("]"); + return sb.toString(); + } + + /** + * Returns the entry name which contains the path from the base directory. + *

+ * This does not return the full path of the file, but the path relative to + * the FileIoSource directory. + *

+ */ + public String getName() { + return entryName; + } + + /** + * Return the time the file was last modified. + */ + public long lastModified() { + return con.getLastModified(); + } + + /** + * Return the size of the file. + */ + public long size() { + return con.getContentLength(); + } + + /** + * Return the input stream for this file. + */ + public InputStream getInputStream() throws IOException { + + return con.getInputStream(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java new file mode 100644 index 000000000..9903a7b09 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.resource; + +import java.net.MalformedURLException; +import java.net.URL; + +import javax.servlet.ServletContext; + +import com.avaje.ebeaninternal.server.lib.util.GeneralException; + +/** + * A file system directory represented as a FileSource. + */ +public class UrlResourceSource extends AbstractResourceSource implements ResourceSource { + + + ServletContext sc; + + String basePath; + + String realPath; + + /** + * Create the source based on a directory name. + */ + public UrlResourceSource(ServletContext sc, String basePath){ + this.sc = sc; + if (basePath == null){ + this.basePath = "/"; + } else { + this.basePath = "/"+basePath+"/"; + } + this.realPath = sc.getRealPath(basePath); + } + + /** + * Returns the "real path" from the ServletContext root. + *

+ * This can be null for unpacked WAR deployment. + *

+ */ + public String getRealPath() { + return realPath; + } + + /** + * Search for the given URL resource and return as ResourceContent. + *

+ * Returns null if the resource is not found. + *

+ */ + public ResourceContent getContent(String entry) { + + try { + URL url = sc.getResource(basePath+entry); + if (url != null){ + return new UrlResourceContent(url, entry); + } + return null; + + } catch (MalformedURLException ex){ + throw new GeneralException(ex); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/package.html b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/package.html new file mode 100644 index 000000000..9ca4bbdba --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/package.html @@ -0,0 +1,13 @@ + + + + AvajeLib + + +Service for reading content from either the file system or servlet context. +

+A service used to read deployment content such as xml files, images etc +taking into account the environment (servlet war file or file system). +

+ + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java new file mode 100644 index 000000000..36b77a9ac --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java @@ -0,0 +1,155 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A buffer especially designed for Busy PooledConnections. + *

+ * All thread safety controlled externally (by PooledConnectionQueue). + *

+ *

+ * It has a set of 'slots' and PooledConnections know which slot they went into + * and this allows for fast addition and removal (by slotId without looping). + * The capacity will increase on demand by the 'growBy' amount. + *

+ * + * @author rbygrave + * + */ +class BusyConnectionBuffer { + + private PooledConnection[] slots; + + private int growBy; + + private int size; + + private int pos = -1; + + /** + * Create the buffer with an initial capacity and fixed growBy. + * We generally do not want the buffer to grow very often. + * + * @param capacity + * the initial capacity + * @param growBy + * the fixed amount to grow the buffer by. + */ + protected BusyConnectionBuffer(int capacity, int growBy) { + this.slots = new PooledConnection[capacity]; + this.growBy = growBy; + } + + /** + * We can only grow (not shrink) the capacity. + */ + private void setCapacity(int newCapacity) { + if (newCapacity > slots.length){ + PooledConnection[] current = this.slots; + this.slots = new PooledConnection[newCapacity]; + System.arraycopy(current, 0, this.slots, 0, current.length); + } + } + + public String toString() { + return Arrays.toString(slots); + } + + protected int getCapacity() { + return slots.length; + } + + protected int size(){ + return size; + } + + protected boolean isEmpty(){ + return size == 0; + } + + protected int add(PooledConnection pc){ + if (size == slots.length){ + // grow the capacity + setCapacity(slots.length + growBy); + } + ++size; + int slot = nextEmptySlot(); + pc.setSlotId(slot); + slots[slot] = pc; + return size; + } + + protected boolean remove(PooledConnection pc) { + --size; + int slotId = pc.getSlotId(); + if (slots[slotId] != pc){ + return false; + } + slots[slotId] = null; + return true; + } + + + /** + * Get a shallow read only List of the busy connections. + *

+ * Note that the {@link #remove(PooledConnection)} MUST be used to remove PooledConnections. + *

+ * @return + */ + protected List getShallowCopy() { + ArrayList tmp = new ArrayList(); + for (int i = 0; i < slots.length; i++) { + if (slots[i] != null){ + tmp.add(slots[i]); + } + } + return Collections.unmodifiableList(tmp); + } + + /** + * Return the position of the next empty slot. + */ + private int nextEmptySlot() { + + // search forward + while(++pos < slots.length) { + if (slots[pos] == null){ + return pos; + } + } + // search from beginning + pos = -1; + while(++pos < slots.length) { + if (slots[pos] == null){ + return pos; + } + } + + // not expecting this + throw new RuntimeException("No Empty Slot Found?"); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceAlertListener.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceAlertListener.java new file mode 100644 index 000000000..ea64e9026 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceAlertListener.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + + +/** + * Listens for alerting events such as DataSource down. + */ +public interface DataSourceAlertListener { + + /** + * Send an Alert saying the dataSource is down. + */ + public void dataSourceDown(String dataSourceName); + + /** + * Send an Alert saying the dataSource is back up. + */ + public void dataSourceUp(String dataSourceName); + + /** + * Send an Alert saying the dataSource has reached a high + * number of connections. + */ + public void warning(String subject, String msg); + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java new file mode 100644 index 000000000..f5a6ead63 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + + + +/** + * A general DataSource exception. + */ +public class DataSourceException extends RuntimeException +{ + static final long serialVersionUID = 7061559938704539844L; + + public DataSourceException(Exception cause) { + super(cause); + } + + public DataSourceException(String s, Exception cause) { + super(s, cause); + } + + public DataSourceException(String s) { + super(s); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceGlobalManager.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceGlobalManager.java new file mode 100644 index 000000000..eceff6ccb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceGlobalManager.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.util.List; + +import com.avaje.ebean.config.DataSourceConfig; + +/** + * Manages access to named DataSources using singleton scope. + */ +public final class DataSourceGlobalManager { + + private static final DataSourceManager manager = new DataSourceManager(); + + private DataSourceGlobalManager() { + } + + /** + * Return true when the dataSource is shutting down. + */ + public static boolean isShuttingDown() { + return manager.isShuttingDown(); + } + + /** + * Shutdown the dataSources. + */ + public static void shutdown() { + manager.shutdown(); + } + + /** + * Return the list of DataSourcePool's. + */ + public static List getPools() { + return manager.getPools(); + } + + /** + * Return a DataSource pool by its name. + */ + public static DataSourcePool getDataSource(String name) { + return manager.getDataSource(name); + } + + public static DataSourcePool getDataSource(String name, DataSourceConfig dsConfig) { + return manager.getDataSource(name, dsConfig); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceManager.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceManager.java new file mode 100644 index 000000000..8c6b55608 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceManager.java @@ -0,0 +1,250 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.config.DataSourceConfig; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.server.lib.BackgroundRunnable; +import com.avaje.ebeaninternal.server.lib.BackgroundThread; + + +/** + * Manages access to named DataSources. + */ +public class DataSourceManager implements DataSourceNotify { + + private static final Logger logger = Logger.getLogger(DataSourceManager.class.getName()); + + /** + * An alerter that notifies when the database has problems. + */ + private final DataSourceAlertListener alertlistener; + + /** + * Cache of the named DataSources. + */ + private final Hashtable dsMap = new Hashtable(); + + /** + * Monitor for creating dataSources. + */ + private final Object monitor = new Object(); + + /** + * The database checker registered with BackgroundThread. + */ + private final BackgroundRunnable dbChecker; + + /** + * The frequency to test db while it is up. + */ + private final int dbUpFreqInSecs; + + /** + * The frequency to test db while it is down. + */ + private final int dbDownFreqInSecs; + + /** + * Set to true when shutting down. + */ + private boolean shuttingDown; + + private boolean deregisterDriver; + + /** + * Construct with explicit ConfigProperties. + */ + public DataSourceManager() { + + this.alertlistener = createAlertListener(); + + // perform heart beat every 30 seconds by default + this.dbUpFreqInSecs = GlobalProperties.getInt("datasource.heartbeatfreq",30); + this.dbDownFreqInSecs = GlobalProperties.getInt("datasource.deadbeatfreq",10); + this.dbChecker = new BackgroundRunnable(new Checker(), dbUpFreqInSecs); + this.deregisterDriver = GlobalProperties.getBoolean("datasource.deregisterDriver", true); + + try { + BackgroundThread.add(dbChecker); + + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + } + } + + private DataSourceAlertListener createAlertListener() throws DataSourceException { + + String alertCN = GlobalProperties.get("datasource.alert.class", null); + if (alertCN == null){ + return new SimpleAlerter(); + + } else { + try { + return (DataSourceAlertListener)ClassUtil.newInstance(alertCN, this.getClass()); + + } catch (Exception ex){ + throw new DataSourceException(ex); + } + } + } + + /** + * Send an alert to say the dataSource is back up. + */ + public void notifyDataSourceUp(String dataSourceName){ + + dbChecker.setFreqInSecs(dbUpFreqInSecs); + + if (alertlistener != null){ + alertlistener.dataSourceUp(dataSourceName); + } + } + + /** + * Send an alert to say the dataSource is down. + */ + public void notifyDataSourceDown(String dataSourceName){ + + dbChecker.setFreqInSecs(dbDownFreqInSecs); + + if (alertlistener != null){ + alertlistener.dataSourceDown(dataSourceName); + } + } + + /** + * Send an alert to say the dataSource is getting close to its max size. + */ + public void notifyWarning(String subject, String msg){ + if (alertlistener != null){ + alertlistener.warning(subject, msg); + } + } + + /** + * Return true when the dataSource is shutting down. + */ + public boolean isShuttingDown() { + synchronized(monitor) { + return shuttingDown; + } + } + + /** + * Shutdown the dataSources. + */ + public void shutdown() { + + synchronized(monitor) { + + this.shuttingDown = true; + + Collection values = dsMap.values(); + for (DataSourcePool ds : values) { + try { + ds.shutdown(); + } catch (DataSourceException e) { + // should never be thrown as the DataSources are all created... + logger.log(Level.SEVERE, null, e); + } + } + if (deregisterDriver){ + for (DataSourcePool ds : values) { + ds.deregisterDriver(); + } + } + } + } + + /** + * Return the DataSourcePool's. + */ + public List getPools() { + synchronized(monitor) { + // create a copy of the DataSourcePool's + ArrayList list = new ArrayList(); + list.addAll(dsMap.values()); + return list; + } + } + + /** + * Get the dataSource using the default ConfigProperties. + */ + public DataSourcePool getDataSource(String name) { + return getDataSource(name, null); + } + + + public DataSourcePool getDataSource(String name, DataSourceConfig dsConfig){ + + if (name == null){ + throw new IllegalArgumentException("name not defined"); + } + + synchronized(monitor){ + DataSourcePool pool = dsMap.get(name); + if (pool == null){ + if (dsConfig == null){ + dsConfig = new DataSourceConfig(); + dsConfig.loadSettings(name); + } + pool = new DataSourcePool(this, name, dsConfig); + dsMap.put(name, pool); + } + return pool; + } + } + + /** + * Check that the database is up by performing a simple query. This should + * be done periodically. By default every 30 seconds. + */ + private void checkDataSource() { + + synchronized (monitor) { + if (!isShuttingDown()) { + Iterator it = dsMap.values().iterator(); + while (it.hasNext()) { + DataSourcePool ds = it.next(); + ds.checkDataSource(); + } + } + } + } + + /** + * Runs every dbUpFreqInSecs to make sure dataSource is up. + */ + private final class Checker implements Runnable { + + public void run() { + checkDataSource(); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceNotify.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceNotify.java new file mode 100644 index 000000000..c2e12704c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceNotify.java @@ -0,0 +1,27 @@ +package com.avaje.ebeaninternal.server.lib.sql; + +/** + * Listener for notifications about the DataSource such as when the DataSource + * goes down, up or gets close to it's maximum size. + *

+ * The intention is to send email notifications to an administrator (or similar) + * when these events occur on the DataSource. + *

+ */ +public interface DataSourceNotify { + + /** + * Send an alert to say the dataSource is back up. + */ + public void notifyDataSourceUp(String dataSourceName); + + /** + * Send an alert to say the dataSource is down. + */ + public void notifyDataSourceDown(String dataSourceName); + + /** + * Send an alert to say the dataSource is getting close to its max size. + */ + public void notifyWarning(String subject, String msg); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java new file mode 100644 index 000000000..570703404 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java @@ -0,0 +1,927 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import com.avaje.ebean.config.DataSourceConfig; +import com.avaje.ebeaninternal.api.ClassUtil; + +/** + * A robust DataSource. + *

+ *

    + *
  • Manages the number of connections closing connections that have been idle + * for some time. + *
  • Notifies when the datasource goes down and comes back up. + *
  • Checks for expected downtime which is useful for schedule db backups. + *
  • Provides PreparedStatement caching + *
  • Knows the busy connections + *
  • Traces connections that have been leaked + *
+ *

+ */ +public class DataSourcePool implements DataSource { + + private static final Logger logger = Logger.getLogger(DataSourcePool.class.getName()); + + /** + * The name given to this dataSource. + */ + private final String name; + + /** + * Used to notify of changes to the DataSource status. + */ + private final DataSourceNotify notify; + + /** + * Optional listener that can be notified when connections are got from and + * put back into the pool. + */ + private final DataSourcePoolListener poolListener; + + /** + * Properties used to create a Connection. + */ + private final Properties connectionProps; + + /** + * The jdbc connection url. + */ + private final String databaseUrl; + + /** + * The jdbc driver. + */ + private final String databaseDriver; + + /** + * The sql used to test a connection. + */ + private final String heartbeatsql; + + /** + * The transaction isolation level as per java.sql.Connection. + */ + private final int transactionIsolation; + + /** + * The default autoCommit setting for Connections in this pool. + */ + private final boolean autoCommit; + + /** + * Flag set to true to capture stackTraces (can be expensive). + */ + private boolean captureStackTrace; + + /** + * The max size of the stack trace to report. + */ + private int maxStackTraceSize; + + /** + * flag to indicate we have sent an alert message. + */ + private boolean dataSourceDownAlertSent; + + /** + * The time the pool was last trimmed. + */ + private long lastTrimTime; + + /** + * Assume that the DataSource is up. heartBeat checking will discover when + * it goes down, and comes back up again. + */ + private boolean dataSourceUp = true; + + /** + * The current alert. + */ + private boolean inWarningMode; + + /** + * The minimum number of connections this pool will maintain. + */ + private int minConnections; + + /** + * The maximum number of connections this pool will grow to. + */ + private int maxConnections; + + /** + * The number of connections to exceed before a warning Alert is fired. + */ + private int warningSize; + + /** + * The time a thread will wait for a connection to become available. + */ + private int waitTimeoutMillis; + + /** + * The size of the preparedStatement cache; + */ + private int pstmtCacheSize; + + /** + * By default trim connections that are inactive for longer than this time. + */ + private int maxInactiveTimeSecs; + + private final PooledConnectionQueue queue; + + /** + * Used to find and close() leaked connections. Leaked connections are + * thought to be busy but have not been used for some time. Each time a + * connection is used it sets it's lastUsedTime. + */ + private long leakTimeMinutes; + + public DataSourcePool(DataSourceNotify notify, String name, DataSourceConfig params) { + + this.notify = notify; + this.name = name; + this.poolListener = createPoolListener(params.getPoolListener()); + + this.autoCommit = false; + this.transactionIsolation = params.getIsolationLevel(); + + this.maxInactiveTimeSecs = params.getMaxInactiveTimeSecs(); + this.leakTimeMinutes = params.getLeakTimeMinutes(); + this.captureStackTrace = params.isCaptureStackTrace(); + this.maxStackTraceSize = params.getMaxStackTraceSize(); + this.databaseDriver = params.getDriver(); + this.databaseUrl = params.getUrl(); + this.pstmtCacheSize = params.getPstmtCacheSize(); + + this.minConnections = params.getMinConnections(); + this.maxConnections = params.getMaxConnections(); + this.waitTimeoutMillis = params.getWaitTimeoutMillis(); + this.heartbeatsql = params.getHeartbeatSql(); + + queue = new PooledConnectionQueue(this); + + String un = params.getUsername(); + String pw = params.getPassword(); + if (un == null) { + throw new RuntimeException("DataSource user is null?"); + } + if (pw == null) { + throw new RuntimeException("DataSource password is null?"); + } + this.connectionProps = new Properties(); + this.connectionProps.setProperty("user", un); + this.connectionProps.setProperty("password", pw); + + Map customProperties = params.getCustomProperties(); + if (customProperties != null){ + Set> entrySet = customProperties.entrySet(); + for (Entry entry : entrySet) { + this.connectionProps.setProperty(entry.getKey(), entry.getValue()); + } + } + + try { + initialise(); + } catch (SQLException ex) { + throw new DataSourceException(ex); + } + } + + /** + * Create the DataSourcePoolListener if there is one. + */ + private DataSourcePoolListener createPoolListener(String cn) { + if (cn == null) { + return null; + } + try { + return (DataSourcePoolListener)ClassUtil.newInstance(cn, this.getClass()); + } catch (Exception e) { + throw new DataSourceException(e); + } + } + + private void initialise() throws SQLException { + + // Ensure database driver is loaded + try { + ClassUtil.forName(this.databaseDriver, this.getClass()); + } catch (Throwable e) { + throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: " + + e.getMessage(), e); + } + + String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation); + StringBuilder sb = new StringBuilder(); + sb.append("DataSourcePool [").append(name); + sb.append("] autoCommit[").append(autoCommit); + sb.append("] transIsolation[").append(transIsolation); + sb.append("] min[").append(minConnections); + sb.append("] max[").append(maxConnections).append("]"); + + logger.info(sb.toString()); + + queue.ensureMinimumConnections(); + } + + /** + * Returns false. + */ + public boolean isWrapperFor(Class arg0) throws SQLException { + return false; + } + + /** + * Not Implemented. + */ + public T unwrap(Class arg0) throws SQLException { + throw new SQLException("Not Implemented"); + } + + /** + * Return the dataSource name. + */ + public String getName() { + return name; + } + + /** + * Return the max size of stack traces used when trying to find connection pool leaks. + *

+ * This is only used when {@link #isCaptureStackTrace()} is true. + *

+ */ + public int getMaxStackTraceSize() { + return maxStackTraceSize; + } + + /** + * Returns false when the dataSource is down. + */ + public boolean isDataSourceUp() { + return dataSourceUp; + } + + /** + * Called when the pool hits the warning level. + */ + protected void notifyWarning(String msg) { + + if (!inWarningMode) { + // send an Error to the event log... + inWarningMode = true; + logger.warning(msg); + if (notify != null) { + String subject = "DataSourcePool [" + name + "] warning"; + notify.notifyWarning(subject, msg); + } + } + } + + private void notifyDataSourceIsDown(SQLException ex) { + + if (!dataSourceDownAlertSent) { + String msg = "FATAL: DataSourcePool [" + name + "] is down!!!"; + logger.log(Level.SEVERE, msg, ex); + if (notify != null) { + notify.notifyDataSourceDown(name); + } + dataSourceDownAlertSent = true; + + } + if (dataSourceUp) { + reset(); + } + dataSourceUp = false; + } + + private void notifyDataSourceIsUp() { + if (dataSourceDownAlertSent) { + String msg = "RESOLVED FATAL: DataSourcePool [" + name + "] is back up!"; + logger.log(Level.SEVERE, msg); + if (notify != null) { + notify.notifyDataSourceUp(name); + } + dataSourceDownAlertSent = false; + + } else if (!dataSourceUp) { + logger.log(Level.WARNING, "DataSourcePool [" + name + "] is back up!"); + } + + if (!dataSourceUp) { + dataSourceUp = true; + reset(); + } + } + + /** + * Check the dataSource is up. Trim connections. + */ + protected void checkDataSource() { + Connection conn = null; + try { + // test to see if we can create a new connection... + conn = getConnection(); + testConnection(conn); + + notifyDataSourceIsUp(); + + if (System.currentTimeMillis() > (lastTrimTime + (maxInactiveTimeSecs * 1000))) { + queue.trim(maxInactiveTimeSecs); + lastTrimTime = System.currentTimeMillis(); + } + + } catch (SQLException ex) { + notifyDataSourceIsDown(ex); + } finally { + try { + if (conn != null) { + conn.close(); + } + } catch (SQLException ex) { + logger.log(Level.WARNING, "Can't close connection in checkDataSource!"); + } + } + } + + /** + * Create a Connection that will not be part of the connection pool. + * + *

+ * When this connection is closed it will not go back into the pool. + *

+ * + *

+ * If withDefaults is true then the Connection will have the autoCommit and + * transaction isolation set to the defaults for the pool. + *

+ */ + public Connection createUnpooledConnection() throws SQLException { + + try { + Connection conn = DriverManager.getConnection(databaseUrl, connectionProps); + conn.setAutoCommit(autoCommit); + conn.setTransactionIsolation(transactionIsolation); + return conn; + + } catch (SQLException ex) { + notifyDataSourceIsDown(null); + throw ex; + } + } + + /** + * Set a new maximum size. The pool should respect this new maximum + * immediately and not require a restart. You may want to increase the + * maxConnections if the pool gets large and hits the warning level. + */ + public void setMaxSize(int max) { + queue.setMaxSize(max); + this.maxConnections = max; + } + + /** + * Return the max size this pool can grow to. + */ + public int getMaxSize() { + return maxConnections; + } + + /** + * Set the min size this pool should maintain. + */ + public void setMinSize(int min) { + queue.setMinSize(min); + this.minConnections = min; + } + + /** + * Return the min size this pool should maintain. + */ + public int getMinSize() { + return minConnections; + } + + /** + * Set a new maximum size. The pool should respect this new maximum + * immediately and not require a restart. You may want to increase the + * maxConnections if the pool gets large and hits the warning and or alert + * levels. + */ + public void setWarningSize(int warningSize) { + queue.setWarningSize(warningSize); + this.warningSize = warningSize; + } + + /** + * Return the warning size. When the pool hits this size it can send a + * notify message to an administrator. + */ + public int getWarningSize() { + return warningSize; + } + + /** + * Return the time in millis that threads will wait when the pool has hit + * the max size. These threads wait for connections to be returned by the + * busy connections. + */ + public int getWaitTimeoutMillis() { + return waitTimeoutMillis; + } + + /** + * Set the time after which inactive connections are trimmed. + */ + public void setMaxInactiveTimeSecs(int maxInactiveTimeSecs) { + this.maxInactiveTimeSecs = maxInactiveTimeSecs; + } + + /** + * Return the time after which inactive connections are trimmed. + */ + public int getMaxInactiveTimeSecs() { + return maxInactiveTimeSecs; + } + + private void testConnection(Connection conn) throws SQLException { + + if (heartbeatsql == null) { + return; + } + Statement stmt = null; + ResultSet rset = null; + try { + // It should only error IF the DataSource is down ? (or a network + // issue?) + stmt = conn.createStatement(); + rset = stmt.executeQuery(heartbeatsql); + conn.commit(); + + } finally { + try { + if (rset != null) { + rset.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + try { + if (stmt != null) { + stmt.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + } + + /** + * Make sure the connection is still ok to use. If not then remove it from + * the pool. + */ + protected boolean validateConnection(PooledConnection conn) { + try { + if (heartbeatsql == null) { + logger.info("Can not test connection as heartbeatsql is not set"); + return false; + } + + testConnection(conn); + return true; + + } catch (Exception e) { + String desc = "heartbeatsql test failed on connection[" + conn.getName() + "]"; + logger.warning(desc); + return false; + } + } + + /** + * Called by the PooledConnection themselves, returning themselves to the + * pool when they have been finished with. + *

+ * Note that connections may not be added back to the pool if returnToPool + * is false or if they where created before the recycleTime. In both of + * these cases the connection is fully closed and not pooled. + *

+ * + * @param pooledConnection + * the returning connection + * + */ + protected void returnConnection(PooledConnection pooledConnection) { + + if (poolListener != null) { + poolListener.onBeforeReturnConnection(pooledConnection); + } + queue.returnPooledConnection(pooledConnection); + } + + /** + * Returns information describing connections that are currently being used. + */ + public String getBusyConnectionInformation() { + + return queue.getBusyConnectionInformation(); + } + + /** + * Dumps the busy connection information to the logs. + *

+ * This includes the stackTrace elements if they are being captured. This is + * useful when needing to look a potential connection pool leaks. + *

+ */ + public void dumpBusyConnectionInformation() { + + queue.dumpBusyConnectionInformation(); + } + + /** + * Close any busy connections that have not been used for some time. + *

+ * These connections are considered to have leaked from the connection pool. + *

+ *

+ * Connection leaks occur when code doesn't ensure that connections are + * closed() after they have been finished with. There should be an + * appropriate try catch finally block to ensure connections are always + * closed and put back into the pool. + *

+ */ + public void closeBusyConnections(long leakTimeMinutes) { + + queue.closeBusyConnections(leakTimeMinutes); + } + + /** + * Grow the pool by creating a new connection. The connection can either be + * added to the available list, or returned. + *

+ * This method is protected by synchronisation in calling methods. + *

+ */ + protected PooledConnection createConnectionForQueue(int connId) throws SQLException { + + try { + Connection c = createUnpooledConnection(); + + PooledConnection pc = new PooledConnection(this, connId, c); + pc.resetForUse(); + + if (!dataSourceUp) { + notifyDataSourceIsUp(); + } + return pc; + + } catch (SQLException ex) { + notifyDataSourceIsDown(ex); + throw ex; + } + + } + + /** + * Close all the connections in the pool. + *

+ *

    + *
  • Checks that the database is up. + *
  • Resets the Alert level. + *
  • Closes busy connections that have not been used for some time (aka + * leaks). + *
  • This closes all the currently available connections. + *
  • Busy connections are closed when they are returned to the pool. + *
+ *

+ */ + public void reset() { + queue.reset(leakTimeMinutes); + inWarningMode = false; + } + + /** + * Return a pooled connection. + */ + public Connection getConnection() throws SQLException { + return getPooledConnection(); + } + + /** + * Get a connection from the pool. + *

+ * This will grow the pool if all the current connections are busy. This + * will go into a wait if the pool has hit its maximum size. + *

+ */ + public PooledConnection getPooledConnection() throws SQLException { + + PooledConnection c = queue.getPooledConnection(); + + if (captureStackTrace) { + c.setStackTrace(Thread.currentThread().getStackTrace()); + } + + if (poolListener != null) { + poolListener.onAfterBorrowConnection(c); + } + return c; + } + + /** + * Send a message to the DataSourceAlertListener to test it. This is so that + * you can make sure the alerter is configured correctly etc. + */ + public void testAlert() { + + String subject = "Test DataSourcePool [" + name + "]"; + String msg = "Just testing if alert message is sent successfully."; + + if (notify != null) { + notify.notifyWarning(subject, msg); + } + } + + /** + * This will close all the free connections, and then go into a wait loop, + * waiting for the busy connections to be freed. + * + *

+ * The DataSources's should be shutdown AFTER thread pools. Leaked + * Connections are not waited on, as that would hang the server. + *

+ */ + public void shutdown() { + queue.shutdown(); + } + + /** + * Return the default autoCommit setting Connections in this pool will use. + * + * @return true if the pool defaults autoCommit to true + */ + public boolean getAutoCommit() { + return autoCommit; + } + + /** + * Return the default transaction isolation level connections in this pool + * should have. + * + * @return the default transaction isolation level + */ + public int getTransactionIsolation() { + return transactionIsolation; + } + + /** + * Return true if the connection pool is currently capturing the StackTrace + * when connections are 'got' from the pool. + *

+ * This is set to true to help diagnose connection pool leaks. + *

+ */ + public boolean isCaptureStackTrace() { + return captureStackTrace; + } + + /** + * Set this to true means that the StackElements are captured every time a + * connection is retrieved from the pool. This can be used to identify + * connection pool leaks. + */ + public void setCaptureStackTrace(boolean captureStackTrace) { + this.captureStackTrace = captureStackTrace; + } + + /** + * Not implemented and shouldn't be used. + */ + public Connection getConnection(String username, String password) throws SQLException { + throw new SQLException("Method not supported"); + } + + /** + * Not implemented and shouldn't be used. + */ + public int getLoginTimeout() throws SQLException { + throw new SQLException("Method not supported"); + } + + /** + * Not implemented and shouldn't be used. + */ + public void setLoginTimeout(int seconds) throws SQLException { + throw new SQLException("Method not supported"); + } + + /** + * Returns null. + */ + public PrintWriter getLogWriter() { + return null; + } + + /** + * Not implemented. + */ + public void setLogWriter(PrintWriter writer) throws SQLException { + throw new SQLException("Method not supported"); + } + + /** + * For detecting and closing leaked connections. Connections that have been + * busy for more than leakTimeMinutes are considered leaks and will be + * closed on a reset(). + *

+ * If you want to use a connection for that longer then you should consider + * creating an unpooled connection or setting longRunning to true on that + * connection. + *

+ */ + public void setLeakTimeMinutes(long leakTimeMinutes) { + this.leakTimeMinutes = leakTimeMinutes; + } + + /** + * Return the number of minutes after which a busy connection could be + * considered leaked from the connection pool. + */ + public long getLeakTimeMinutes() { + return leakTimeMinutes; + } + + /** + * Return the preparedStatement cache size. + */ + public int getPstmtCacheSize() { + return pstmtCacheSize; + } + + /** + * Set the preparedStatement cache size. + */ + public void setPstmtCacheSize(int pstmtCacheSize) { + this.pstmtCacheSize = pstmtCacheSize; + } + + /** + * Return the current status of the connection pool. + *

+ * If you pass reset = true then the counters such as + * hitCount, waitCount and highWaterMark are reset. + *

+ */ + public Status getStatus(boolean reset) { + return queue.getStatus(reset); + } + + /** + * Deregister the JDBC driver. + */ + public void deregisterDriver() { + try { + DriverManager.deregisterDriver(DriverManager.getDriver(this.databaseUrl)); + String msg = "Deregistered the JDBC driver "+this.databaseDriver; + logger.log(Level.FINE, msg); + } catch (SQLException e) { + String msg = "Error trying to deregister the JDBC driver "+this.databaseDriver; + logger.log(Level.WARNING, msg, e); + } + } + + public static class Status { + + private final String name; + private final int minSize; + private final int maxSize; + private final int free; + private final int busy; + private final int waiting; + private final int highWaterMark; + private final int waitCount; + private final int hitCount; + + protected Status(String name, int minSize, int maxSize, int free, int busy, int waiting, int highWaterMark, + int waitCount, int hitCount) { + this.name = name; + this.minSize = minSize; + this.maxSize = maxSize; + this.free = free; + this.busy = busy; + this.waiting = waiting; + this.highWaterMark = highWaterMark; + this.waitCount = waitCount; + this.hitCount = hitCount; + } + + public String toString() { + return "min:" + minSize + " max:" + maxSize + " free:" + free + " busy:" + busy + " waiting:" + waiting + + " highWaterMark:" + highWaterMark + " waitCount:" + waitCount + " hitCount:" + hitCount; + } + + /** + * Return the DataSource name. + */ + public String getName() { + return name; + } + + /** + * Return the min pool size. + */ + public int getMinSize() { + return minSize; + } + + /** + * Return the max pool size. + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Return the current number of free connections in the pool. + */ + public int getFree() { + return free; + } + + /** + * Return the current number of busy connections in the pool. + */ + public int getBusy() { + return busy; + } + + /** + * Return the current number of threads waiting for a connection. + */ + public int getWaiting() { + return waiting; + } + + /** + * Return the high water mark of busy connections. + */ + public int getHighWaterMark() { + return highWaterMark; + } + + /** + * Return the total number of times a thread had to wait. + */ + public int getWaitCount() { + return waitCount; + } + + /** + * Return the total number of times there was an attempt to get a + * connection. + *

+ * If the attempt to get a connection failed with a timeout or other + * exception those attempts are still included in this hit count. + *

+ */ + public int getHitCount() { + return hitCount; + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePoolListener.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePoolListener.java new file mode 100644 index 000000000..0ae328fc5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePoolListener.java @@ -0,0 +1,34 @@ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.sql.Connection; + + +/** + * A {@link DataSourcePool} listener which allows you to hook on the + * borrow/return process of getting or returning connections from the pool. + *

+ * In the configuration use the poolListener key to configure which listener to + * use. + *

+ *

+ * Example: datasource.ora10.poolListener=my.very.fancy.PoolListener + *

+ * + *

+ * Notice: This listener only works if you are using the default Avaje + * {@link DataSourcePool}. + *

+ */ +public interface DataSourcePoolListener { + + /** + * Called after a connection has been retrieved from the connection pool + */ + public void onAfterBorrowConnection(Connection c); + + /** + * Called before a connection will be put back to the connection pool + */ + public void onBeforeReturnConnection(Connection c); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java new file mode 100644 index 000000000..6d6204832 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java @@ -0,0 +1,407 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; + +/** + * Extended PreparedStatement that supports caching. + *

+ * Designed so that it can be cached by the PooledConnection. It additionally + * notes any Exceptions that occur and this is used to ensure bad connections + * are removed from the connection pool. + *

+ */ +public class ExtendedPreparedStatement extends ExtendedStatement implements PreparedStatement { + + /** + * The SQL used to create the underlying PreparedStatement. + */ + final String sql; + + /** + * The key used to cache this in the connection. + */ + final String cacheKey; + + /** + * Create a wrapped PreparedStatement that can be cached. + */ + public ExtendedPreparedStatement(PooledConnection pooledConnection, PreparedStatement pstmt, + String sql, String cacheKey) { + super(pooledConnection, pstmt); + this.sql = sql; + this.cacheKey = cacheKey; + } + + public PreparedStatement getDelegate() { + return pstmt; + } + + /** + * Return the key used to cache this on the Connection. + */ + public String getCacheKey() { + return cacheKey; + } + + /** + * Return the SQL used to create this PreparedStatement. + */ + public String getSql() { + return sql; + } + + /** + * Fully close the underlying PreparedStatement. After this we can no longer + * reuse the PreparedStatement. + */ + public void closeDestroy() throws SQLException { + pstmt.close(); + } + + /** + * Returns the PreparedStatement back into the cache. This doesn't fully + * close the underlying PreparedStatement. + */ + public void close() throws SQLException { + // return the connection back into the cache. + pooledConnection.returnPreparedStatement(this); + } + + /** + * Add the last binding for batch execution. + */ + public void addBatch() throws SQLException { + try { + pstmt.addBatch(); + } catch (SQLException e) { + // we got an error... need to check this + // connection before returning it + pooledConnection.addError(e); + throw e; + } + } + + /** + * Clear parameters. + */ + public void clearParameters() throws SQLException { + try { + pstmt.clearParameters(); + } catch (SQLException e) { + // we got an error... need to check + // this connection before returning it + pooledConnection.addError(e); + throw e; + } + } + + /** + * execute the statement. + */ + public boolean execute() throws SQLException { + try { + return pstmt.execute(); + } catch (SQLException e) { + // we got an error... need to check + // this connection before returning it + pooledConnection.addError(e); + throw e; + } + } + + /** + * Execute teh query. + */ + public ResultSet executeQuery() throws SQLException { + try { + return pstmt.executeQuery(); + } catch (SQLException e) { + // we got an error... need to check + // this connection before returning it + pooledConnection.addError(e); + throw e; + } + } + + /** + * Execute the dml statement. + */ + public int executeUpdate() throws SQLException { + try { + return pstmt.executeUpdate(); + } catch (SQLException e) { + // we got an error... need to check + // this connection before returning it + pooledConnection.addError(e); + throw e; + } + } + + /** + * Return the MetaData for the query. + */ + public ResultSetMetaData getMetaData() throws SQLException { + try { + return pstmt.getMetaData(); + } catch (SQLException e) { + // we got an error... need to check + // this connection before returning it + pooledConnection.addError(e); + throw e; + } + } + + /** + * Standard PreparedStatement method execution. + */ + public ParameterMetaData getParameterMetaData() throws SQLException { + return pstmt.getParameterMetaData(); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setArray(int i, Array x) throws SQLException { + pstmt.setArray(i, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + pstmt.setAsciiStream(parameterIndex, x, length); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + pstmt.setBigDecimal(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + pstmt.setBinaryStream(parameterIndex, x, length); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setBlob(int i, Blob x) throws SQLException { + pstmt.setBlob(i, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + pstmt.setBoolean(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setByte(int parameterIndex, byte x) throws SQLException { + pstmt.setByte(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + pstmt.setBytes(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setCharacterStream(int parameterIndex, Reader reader, int length) + throws SQLException { + pstmt.setCharacterStream(parameterIndex, reader, length); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setClob(int i, Clob x) throws SQLException { + pstmt.setClob(i, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setDate(int parameterIndex, Date x) throws SQLException { + pstmt.setDate(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + pstmt.setDate(parameterIndex, x, cal); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setDouble(int parameterIndex, double x) throws SQLException { + pstmt.setDouble(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setFloat(int parameterIndex, float x) throws SQLException { + pstmt.setFloat(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setInt(int parameterIndex, int x) throws SQLException { + pstmt.setInt(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setLong(int parameterIndex, long x) throws SQLException { + pstmt.setLong(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setNull(int parameterIndex, int sqlType) throws SQLException { + pstmt.setNull(parameterIndex, sqlType); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setNull(int paramIndex, int sqlType, String typeName) throws SQLException { + pstmt.setNull(paramIndex, sqlType, typeName); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setObject(int parameterIndex, Object x) throws SQLException { + pstmt.setObject(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + pstmt.setObject(parameterIndex, x, targetSqlType); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) + throws SQLException { + pstmt.setObject(parameterIndex, x, targetSqlType, scale); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setRef(int i, Ref x) throws SQLException { + pstmt.setRef(i, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setShort(int parameterIndex, short x) throws SQLException { + pstmt.setShort(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setString(int parameterIndex, String x) throws SQLException { + pstmt.setString(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setTime(int parameterIndex, Time x) throws SQLException { + pstmt.setTime(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + pstmt.setTime(parameterIndex, x, cal); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + pstmt.setTimestamp(parameterIndex, x); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + pstmt.setTimestamp(parameterIndex, x, cal); + } + + /** + * Standard PreparedStatement method execution. + * @deprecated + */ + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + pstmt.setUnicodeStream(parameterIndex, x, length); + } + + /** + * Standard PreparedStatement method execution. + */ + public void setURL(int parameterIndex, URL x) throws SQLException { + pstmt.setURL(parameterIndex, x); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java new file mode 100644 index 000000000..8d3ab949a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java @@ -0,0 +1,345 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import com.avaje.ebeaninternal.jdbc.PreparedStatementDelegator; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; + +/** + * Implements the Statement methods for ExtendedPreparedStatement. + *

+ * PreparedStatements should always be used and the intention is that there + * should be no use of Statement at all. The implementation here is generally + * for the case where someone uses the Statement api on an ExtendedPreparedStatement. + *

+ */ +public abstract class ExtendedStatement extends PreparedStatementDelegator +{ + + /** + * The pooled connection this Statement belongs to. + */ + protected final PooledConnection pooledConnection; + + /** + * The underlying Statement that this object wraps. + */ + protected final PreparedStatement pstmt; + + /** + * Create the ExtendedStatement for a given pooledConnection. + */ + public ExtendedStatement(PooledConnection pooledConnection, PreparedStatement pstmt) { + super(pstmt); + + this.pooledConnection = pooledConnection; + this.pstmt = pstmt; + } + + /** + * Put the statement back into the statement cache. + */ + public abstract void close() throws SQLException; + + /** + * Return the underlying connection. + */ + public Connection getConnection() throws SQLException { + try { + return pstmt.getConnection(); + } catch (SQLException e) { + pooledConnection.addError(e); + throw e; + } + } + + /** + * Add the sql for batch execution. + */ + public void addBatch(String sql) throws SQLException { + try { + pooledConnection.setLastStatement(sql); + pstmt.addBatch(sql); + } catch (SQLException e) { + pooledConnection.addError(e); + throw e; + } + } + + /** + * Execute the sql. + */ + public boolean execute(String sql) throws SQLException { + try { + pooledConnection.setLastStatement(sql); + return pstmt.execute(sql); + } catch (SQLException e) { + pooledConnection.addError(e); + throw e; + } + } + + /** + * Execute the query. + */ + public ResultSet executeQuery(String sql) throws SQLException { + try { + pooledConnection.setLastStatement(sql); + return pstmt.executeQuery(sql); + } catch (SQLException e) { + pooledConnection.addError(e); + throw e; + } + } + + /** + * Execute the dml sql. + */ + public int executeUpdate(String sql) throws SQLException { + try { + pooledConnection.setLastStatement(sql); + return pstmt.executeUpdate(sql); + } catch (SQLException e) { + pooledConnection.addError(e); + throw e; + } + } + + /** + * Standard Statement method call. + */ + public int[] executeBatch() throws SQLException { + return pstmt.executeBatch(); + } + + /** + * Standard Statement method call. + */ + public void cancel() throws SQLException { + pstmt.cancel(); + } + + /** + * Standard Statement method call. + */ + public void clearBatch() throws SQLException { + pstmt.clearBatch(); + } + + /** + * Standard Statement method call. + */ + public void clearWarnings() throws SQLException { + pstmt.clearWarnings(); + } + + /** + * Standard Statement method call. + */ + public int getFetchDirection() throws SQLException { + return pstmt.getFetchDirection(); + } + + /** + * Standard Statement method call. + */ + public int getFetchSize() throws SQLException { + return pstmt.getFetchSize(); + } + + /** + * Standard Statement method call. + */ + public int getMaxFieldSize() throws SQLException { + return pstmt.getMaxFieldSize(); + } + + /** + * Standard Statement method call. + */ + public int getMaxRows() throws SQLException { + return pstmt.getMaxRows(); + } + + /** + * Standard Statement method call. + */ + public boolean getMoreResults() throws SQLException { + return pstmt.getMoreResults(); + } + + /** + * Standard Statement method call. + */ + public int getQueryTimeout() throws SQLException { + return pstmt.getQueryTimeout(); + } + + /** + * Standard Statement method call. + */ + public ResultSet getResultSet() throws SQLException { + return pstmt.getResultSet(); + } + + /** + * Standard Statement method call. + */ + public int getResultSetConcurrency() throws SQLException { + return pstmt.getResultSetConcurrency(); + } + + /** + * Standard Statement method call. + */ + public int getResultSetType() throws SQLException { + return pstmt.getResultSetType(); + } + + /** + * Standard Statement method call. + */ + public int getUpdateCount() throws SQLException { + return pstmt.getUpdateCount(); + } + + /** + * Standard Statement method call. + */ + public SQLWarning getWarnings() throws SQLException { + return pstmt.getWarnings(); + } + + /** + * Standard Statement method call. + */ + public void setCursorName(String name) throws SQLException { + pstmt.setCursorName(name); + } + + /** + * Standard Statement method call. + */ + public void setEscapeProcessing(boolean enable) throws SQLException { + pstmt.setEscapeProcessing(enable); + } + + /** + * Standard Statement method call. + */ + public void setFetchDirection(int direction) throws SQLException { + pstmt.setFetchDirection(direction); + } + + /** + * Standard Statement method call. + */ + public void setFetchSize(int rows) throws SQLException { + pstmt.setFetchSize(rows); + } + + /** + * Standard Statement method call. + */ + public void setMaxFieldSize(int max) throws SQLException { + pstmt.setMaxFieldSize(max); + } + + /** + * Standard Statement method call. + */ + public void setMaxRows(int max) throws SQLException { + pstmt.setMaxRows(max); + } + + /** + * Standard Statement method call. + */ + public void setQueryTimeout(int seconds) throws SQLException { + pstmt.setQueryTimeout(seconds); + } + + /** + * Standard Statement method call. + */ + public boolean getMoreResults(int i) throws SQLException { + return pstmt.getMoreResults(i); + } + + /** + * Standard Statement method call. + */ + public ResultSet getGeneratedKeys() throws SQLException { + return pstmt.getGeneratedKeys(); + } + + /** + * Standard Statement method call. + */ + public int executeUpdate(String s, int i) throws SQLException { + return pstmt.executeUpdate(s, i); + } + + /** + * Standard Statement method call. + */ + public int executeUpdate(String s, int[] i) throws SQLException { + return pstmt.executeUpdate(s, i); + } + + /** + * Standard Statement method call. + */ + public int executeUpdate(String s, String[] i) throws SQLException { + return pstmt.executeUpdate(s, i); + } + + /** + * Standard Statement method call. + */ + public boolean execute(String s, int i) throws SQLException { + return pstmt.execute(s, i); + } + + /** + * Standard Statement method call. + */ + public boolean execute(String s, int[] i) throws SQLException { + return pstmt.execute(s, i); + } + + /** + * Standard Statement method call. + */ + public boolean execute(String s, String[] i) throws SQLException { + return pstmt.execute(s, i); + } + + /** + * Standard Statement method call. + */ + public int getResultSetHoldability() throws SQLException { + return pstmt.getResultSetHoldability(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java new file mode 100644 index 000000000..b97cd387f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java @@ -0,0 +1,157 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.util.ArrayList; +import java.util.List; + +/** + * A buffer designed especially to hold free pooled connections. + *

+ * It is circular in nature. + *

+ *

+ * All thread safety controlled externally (by PooledConnectionQueue). + *

+ * + * @author rbygrave + * + */ +class FreeConnectionBuffer { + + private PooledConnection[] conns; + + private int removeIndex; + + private int addIndex; + + /** + * The current number of connections in the buffer + */ + private int size; + + protected FreeConnectionBuffer(int capacity) { + this.conns = new PooledConnection[capacity]; + } + + protected int getCapacity() { + return conns.length; + } + + protected int size() { + return size; + } + + protected boolean isEmpty() { + return size == 0; + } + + /** + * Add at connection. + */ + protected void add(PooledConnection pc) { + conns[addIndex] = pc; + addIndex = inc(addIndex); + ++size; + } + + /** + * Remove a connection at current remove position. + */ + protected PooledConnection remove() { + final PooledConnection[] items = this.conns; + PooledConnection pc = items[removeIndex]; + items[removeIndex] = null; + removeIndex = inc(removeIndex); + --size; + return pc; + } + + /** + * Return a shallow copy of the free connections. + */ + protected List getShallowCopy() { + + List copy = new ArrayList(conns.length); + for (int i = 0; i < conns.length; i++) { + if (conns[i] != null){ + copy.add(conns[i]); + } + } + return copy; + } + + /** + * Set the free list to be the connections in this copy. This is done after + * unused connections have been trimmed. + *

+ * Not a particularly performant approach but this should not be called very + * often + *

+ */ + protected void setShallowCopy(List copy) { + + // reset to empty state + this.removeIndex = 0; + this.addIndex = 0; + this.size = 0; + + // null all the current connections + for (int i = 0; i < conns.length; i++) { + conns[i] = null; + } + + // add connections from the copy + for (int i = 0; i < copy.size(); i++) { + add(copy.get(i)); + } + } + + /** + * Increase the capacity of the buffer. This is a relatively expensive + * operation but should occur very infrequently. + */ + protected void setCapacity(int newCapacity) { + if (newCapacity > conns.length){ + + List copy = getShallowCopy(); + + // reset to empty state + this.removeIndex = 0; + this.addIndex = 0; + this.size = 0; + + this.conns = new PooledConnection[newCapacity]; + + // add the connections back from the copy + for (int i = 0; i < copy.size(); i++) { + add(copy.get(i)); + } + } + } + + /** + * Circularly increment i. + */ + private final int inc(int i) { + return (++i == conns.length)? 0 : i; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java new file mode 100644 index 000000000..44189ccd1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java @@ -0,0 +1,961 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import com.avaje.ebeaninternal.jdbc.ConnectionDelegator; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Is a connection that belongs to a DataSourcePool. + * + *

+ * It is designed to be part of DataSourcePool. Closing the connection puts it + * back into the pool. + *

+ * + *

+ * It defaults autoCommit and Transaction Isolation to the defaults of the + * DataSourcePool. + *

+ * + *

+ * It has caching of Statements and PreparedStatements. Remembers the last + * statement that was executed. Keeps statistics on how long it is in use. + *

+ */ +public class PooledConnection extends ConnectionDelegator +{ + + private static final Logger logger = Logger.getLogger(PooledConnection.class.getName()); + + private static String IDLE_CONNECTION_ACCESSED_ERROR = "Pooled Connection has been accessed whilst idle in the pool, via method: "; + + /** + * Set when connection is idle in the pool. In general when in the pool the + * connection should not be modified. + */ + static final int STATUS_IDLE = 88; + + /** + * Set when connection given to client. + */ + static final int STATUS_ACTIVE = 89; + + /** + * Set when commit() or rollback() called. + */ + static final int STATUS_ENDED = 87; + + /** + * Name used to identify the PooledConnection for logging. + */ + final String name; + + /** + * The pool this connection belongs to. + */ + final DataSourcePool pool; + + /** + * The underlying connection. + */ + final Connection connection; + + /** + * The time this connection was created. + */ + final long creationTime; + + /** + * Cache of the PreparedStatements + */ + final PstmtCache pstmtCache; + + final Object pstmtMonitor = new Object(); + + /** + * The status of the connection. IDLE, ACTIVE or ENDED. + */ + int status = STATUS_IDLE; + + /** + * Set this to true if the connection will be busy for a long time. + *

+ * This means it should skip the suspected connection pool leak checking. + *

+ */ + boolean longRunning; + + /** + * Flag to indicate that this connection had errors and should be checked to + * make sure it is okay. + */ + boolean hadErrors; + + /** + * The last start time. When the connection was given to a thread. + */ + long startUseTime; + + /** + * The last end time of this connection. This is to calculate the usage + * time. + */ + long lastUseTime; + + /** + * The last statement executed by this connection. + */ + String lastStatement; + + /** + * The number of hits against the preparedStatement cache. + */ + int pstmtHitCounter; + + /** + * The number of misses against the preparedStatement cache. + */ + int pstmtMissCounter; + + /** + * The non avaje method that created the connection. + */ + String createdByMethod; + + /** + * Used to find connection pool leaks. + */ + StackTraceElement[] stackTrace; + + int maxStackTrace; + + /** + * Slot position in the BusyConnectionBuffer. + */ + int slotId; + + /** + * Construct the connection that can refer back to the pool it belongs to. + *

+ * close() will return the connection back to the pool , while + * closeDestroy() will close() the underlining connection properly. + *

+ */ + public PooledConnection(DataSourcePool pool, int uniqueId, Connection connection) throws SQLException { + super(connection); + + this.pool = pool; + this.connection = connection; + this.name = pool.getName() + "." + uniqueId; + this.pstmtCache = new PstmtCache(name, pool.getPstmtCacheSize()); + this.maxStackTrace = pool.getMaxStackTraceSize(); + this.creationTime = System.currentTimeMillis(); + this.lastUseTime = creationTime; + } + + /** + * For testing the pool without real connections. + */ + protected PooledConnection(String name) { + super(null); + this.name = name; + this.pool = null; + this.connection = null; + this.pstmtCache = null; + this.maxStackTrace = 0; + this.creationTime = System.currentTimeMillis(); + this.lastUseTime = creationTime; + } + + /** + * Return the slot position in the busy buffer. + */ + public int getSlotId() { + return slotId; + } + + /** + * Set the slot position in the busy buffer. + */ + public void setSlotId(int slotId) { + this.slotId = slotId; + } + + /** + * Return the DataSourcePool that this connection belongs to. + */ + public DataSourcePool getDataSourcePool() { + return pool; + } + + /** + * Return the time the connection was created. + */ + public long getCreationTime() { + return creationTime; + } + + /** + * Return a string to identify the connection. + */ + public String getName() { + return name; + } + + public String toString() { + return name; + } + + public String getDescription() { + return "name["+name+"] startTime["+getStartUseTime()+"] stmt["+getLastStatement()+"] createdBy["+getCreatedByMethod()+"]"; + } + + public String getStatistics() { + return "name["+name+"] startTime["+getStartUseTime()+"] pstmtHits["+pstmtHitCounter+"] pstmtMiss["+pstmtMissCounter+"] "+pstmtCache.getDescription(); + } + + /** + * Return true if the connection should be treated as long running (skip connection pool leak check). + */ + public boolean isLongRunning() { + return longRunning; + } + + /** + * Set this to true if the connection is a long running connection and should skip the + * 'suspected connection pool leak' checking. + */ + public void setLongRunning(boolean longRunning) { + this.longRunning = longRunning; + } + + /** + * Close the connection fully NOT putting in back into the pool. + *

+ * The logErrors parameter exists so that expected errors are not logged + * such as when the database is known to be down. + *

+ * + * @param logErrors + * if false then don't log errors when closing + */ + public void closeConnectionFully(boolean logErrors) { + + //pool.removeConnection(this); + + String msg = "Closing Connection[" + getName() + "]" + " psReuse[" + pstmtHitCounter + + "] psCreate[" + pstmtMissCounter + "] psSize[" + pstmtCache.size() + "]"; + + logger.info(msg); + + try { + if (connection.isClosed()) { + msg = "Closing Connection[" + getName() + "] that is already closed?"; + logger.log(Level.SEVERE, msg); + return; + } + } catch (SQLException ex) { + if (logErrors) { + msg = "Error when fully closing connection [" + getName() + "]"; + logger.log(Level.SEVERE, msg, ex); + } + } + + try { + Iterator psi = pstmtCache.values().iterator(); + while (psi.hasNext()) { + ExtendedPreparedStatement ps = (ExtendedPreparedStatement) psi.next(); + ps.closeDestroy(); + } + + } catch (SQLException ex) { + if (logErrors) { + logger.log(Level.WARNING, "Error when closing connection Statements", ex); + } + } + + try { + connection.close(); + + } catch (SQLException ex) { + if (logErrors) { + msg = "Error when fully closing connection [" + getName() + "]"; + logger.log(Level.SEVERE, msg, ex); + } + } + } + + /** + * A Least Recently used cache of PreparedStatements. + */ + public PstmtCache getPstmtCache() { + return pstmtCache; + } + + /** + * Creates a wrapper ExtendedStatement so that I can get the executed sql. I + * want to do this so that I can get the slowest query statments etc, and + * log that information. + */ + public Statement createStatement() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()"); + } + try { + return connection.createStatement(); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public Statement createStatement(int resultSetType, int resultSetConcurreny) + throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()"); + } + try { + return connection.createStatement(resultSetType, resultSetConcurreny); + + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + /** + * Return a PreparedStatement back into the cache. + */ + protected void returnPreparedStatement(ExtendedPreparedStatement pstmt) { + + synchronized (pstmtMonitor) { + ExtendedPreparedStatement alreadyInCache = pstmtCache.get(pstmt.getCacheKey()); + + if (alreadyInCache == null) { + // add the returning prepared statement to the cache. + // Note that the LRUCache will automatically close fully old unused + // PStmts when the cache has hit its maximum size. + pstmtCache.put(pstmt.getCacheKey(), pstmt); + + } else { + try { + // if a entry in the cache exists for the exact same SQL... + // then remove it from the cache and close it fully. + // Only having one PreparedStatement per unique SQL + // statement + pstmt.closeDestroy(); + + } catch (SQLException e) { + logger.log(Level.SEVERE, "Error closing Pstmt", e); + } + } + } + } + + /** + * This will try to use a cache of PreparedStatements. + */ + public PreparedStatement prepareStatement(String sql, int returnKeysFlag) throws SQLException { + String cacheKey = sql + returnKeysFlag; + return prepareStatement(sql, true, returnKeysFlag, cacheKey); + } + + /** + * This will try to use a cache of PreparedStatements. + */ + public PreparedStatement prepareStatement(String sql) throws SQLException { + return prepareStatement(sql, false, 0, sql); + } + + /** + * This will try to use a cache of PreparedStatements. + */ + private PreparedStatement prepareStatement(String sql, boolean useFlag, int flag, String cacheKey) throws SQLException { + + if (status == STATUS_IDLE) { + String m = IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"; + throw new SQLException(m); + } + try { + synchronized (pstmtMonitor) { + lastStatement = sql; + + // try to get a matching cached PStmt from the cache. + ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey); + + if (pstmt != null) { + pstmtHitCounter++; + return pstmt; + } + + // create a new PreparedStatement + pstmtMissCounter++; + PreparedStatement actualPstmt; + if (useFlag) { + actualPstmt = connection.prepareStatement(sql, flag); + } else { + actualPstmt = connection.prepareStatement(sql); + } + return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey); + } + + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) + throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"); + } + try { + // no caching when creating PreparedStatements this way + pstmtMissCounter++; + lastStatement = sql; + return connection.prepareStatement(sql, resultSetType, resultSetConcurreny); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + /** + * Reset the connection for returning to the client. Resets the status, + * startUseTime and hadErrors. + */ + protected void resetForUse() { + this.status = STATUS_ACTIVE; + this.startUseTime = System.currentTimeMillis(); + this.createdByMethod = null; + this.lastStatement = null; + this.hadErrors = false; + this.longRunning = false; + } + + /** + * When an error occurs during use add it the connection. + *

+ * Any PooledConnection that has an error is checked to make sure it works + * before it is placed back into the connection pool. + *

+ */ + public void addError(Throwable e) { + hadErrors = true; + } + + /** + * Returns true if the connect threw any errors during use. + *

+ * Connections with errors are testing to make sure they are still good + * before putting them back into the pool. + *

+ */ + public boolean hadErrors() { + return hadErrors; + } + + /** + * close the connection putting it back into the connection pool. + *

+ * Note that to ensure that the next transaction starts at the correct time + * a commit() or rollback() should be called. If neither has occured at this + * time then a rollback() is used (to end the transaction). + *

+ *

+ * To close the connection fully use closeConnectionFully(). + *

+ */ + public void close() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()"); + } + + if (hadErrors) { + if (!pool.validateConnection(this)) { + // the connection is BAD, close it and test the pool + closeConnectionFully(false); + pool.checkDataSource(); + return; + } + } + + try { + // reset the autoCommit back if client code changed it + if (connection.getAutoCommit() != pool.getAutoCommit()) { + connection.setAutoCommit(pool.getAutoCommit()); + } + // Generally resetting Isolation level seems expensive. + // Hence using resetIsolationReadOnlyRequired flag + // performance reasons. + if (resetIsolationReadOnlyRequired) { + resetIsolationReadOnly(); + resetIsolationReadOnlyRequired = false; + } + + // the connection is assumed GOOD so put it back in the pool + lastUseTime = System.currentTimeMillis(); + // connection.clearWarnings(); + status = STATUS_IDLE; + pool.returnConnection(this); + + } catch (Exception ex) { + // the connection is BAD, close it and test the pool + closeConnectionFully(false); + pool.checkDataSource(); + } + } + + private void resetIsolationReadOnly() throws SQLException { + // reset the transaction isolation if the client code changed it + if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) { + connection.setTransactionIsolation(pool.getTransactionIsolation()); + } + // reset readonly to false + if (connection.isReadOnly()) { + connection.setReadOnly(false); + } + } + + protected void finalize() throws Throwable { + try { + if (connection != null && !connection.isClosed()) { + // connect leak? + String msg = "Closing Connection[" + getName() + "] on finalize()."; + logger.warning(msg); + closeConnectionFully(false); + } + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + } + super.finalize(); + } + + /** + * Return the time the connection was passed to the client code. + *

+ * Used to detect busy connections that could be leaks. + *

+ */ + public long getStartUseTime() { + return startUseTime; + } + + /** + * Returns the time the connection was last used. + *

+ * Used to close connections that have been idle for some time. Typically 5 + * minutes. + *

+ */ + public long getLastUsedTime() { + return lastUseTime; + } + + /** + * Returns the last sql statement executed. + */ + public String getLastStatement() { + return lastStatement; + } + + /** + * Called by ExtendedStatement to trace the sql being executed. + *

+ * Note with addBatch() this will not really work. + *

+ */ + protected void setLastStatement(String lastStatement) { + this.lastStatement = lastStatement; + if (logger.isLoggable(Level.FINER)) { + logger.finer(".setLastStatement[" + lastStatement + "]"); + } + } + + boolean resetIsolationReadOnlyRequired = false; + + /** + * Also note the read only status needs to be reset when put back into the + * pool. + */ + public void setReadOnly(boolean readOnly) throws SQLException { + // A bit loose not checking for STATUS_IDLE + // if (status == STATUS_IDLE) { + // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + + // "setReadOnly()"); + // } + resetIsolationReadOnlyRequired = true; + connection.setReadOnly(readOnly); + } + + /** + * Also note the Isolation level needs to be reset when put back into the + * pool. + */ + public void setTransactionIsolation(int level) throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()"); + } + try { + resetIsolationReadOnlyRequired = true; + connection.setTransactionIsolation(level); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + // + // + // Simple wrapper methods which pass a method call onto the acutal + // connection object. These methods are safe-guarded to prevent use of + // the methods whilst the connection is in the connection pool. + // + // + public void clearWarnings() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()"); + } + connection.clearWarnings(); + } + + public void commit() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()"); + } + try { + status = STATUS_ENDED; + connection.commit(); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public boolean getAutoCommit() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()"); + } + return connection.getAutoCommit(); + } + + public String getCatalog() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()"); + } + return connection.getCatalog(); + } + + public DatabaseMetaData getMetaData() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()"); + } + return connection.getMetaData(); + } + + public int getTransactionIsolation() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()"); + } + return connection.getTransactionIsolation(); + } + + public Map> getTypeMap() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()"); + } + return connection.getTypeMap(); + } + + public SQLWarning getWarnings() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()"); + } + return connection.getWarnings(); + } + + public boolean isClosed() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()"); + } + return connection.isClosed(); + } + + public boolean isReadOnly() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()"); + } + return connection.isReadOnly(); + } + + public String nativeSQL(String sql) throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()"); + } + lastStatement = sql; + return connection.nativeSQL(sql); + } + + public CallableStatement prepareCall(String sql) throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); + } + lastStatement = sql; + return connection.prepareCall(sql); + } + + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) + throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); + } + lastStatement = sql; + return connection.prepareCall(sql, resultSetType, resultSetConcurreny); + } + + public void rollback() throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()"); + } + try { + status = STATUS_ENDED; + connection.rollback(); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public void setAutoCommit(boolean autoCommit) throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()"); + } + try { + connection.setAutoCommit(autoCommit); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public void setCatalog(String catalog) throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()"); + } + connection.setCatalog(catalog); + } + + public void setTypeMap(Map> map) throws SQLException { + if (status == STATUS_IDLE) { + throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()"); + } + connection.setTypeMap(map); + } + + public Savepoint setSavepoint() throws SQLException { + try { + return connection.setSavepoint(); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public Savepoint setSavepoint(String savepointName) throws SQLException { + try { + return connection.setSavepoint(savepointName); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public void rollback(Savepoint sp) throws SQLException { + try { + connection.rollback(sp); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public void releaseSavepoint(Savepoint sp) throws SQLException { + try { + connection.releaseSavepoint(sp); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public void setHoldability(int i) throws SQLException { + try { + connection.setHoldability(i); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public int getHoldability() throws SQLException { + try { + return connection.getHoldability(); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public Statement createStatement(int i, int x, int y) throws SQLException { + try { + return connection.createStatement(i, x, y); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException { + try { + return connection.prepareStatement(s, i, x, y); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public PreparedStatement prepareStatement(String s, int[] i) throws SQLException { + try { + return connection.prepareStatement(s, i); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException { + try { + return connection.prepareStatement(s, s2); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException { + try { + return connection.prepareCall(s, i, x, y); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } + + /** + * Returns the method that created the connection. + *

+ * Used to help finding connection pool leaks. + *

+ */ + public String getCreatedByMethod() { + if (createdByMethod != null) { + return createdByMethod; + } + if (stackTrace == null) { + return null; + } + + for (int j = 0; j < stackTrace.length; j++) { + String methodLine = stackTrace[j].toString(); + if (skipElement(methodLine)) { + // ignore these methods... + } else { + createdByMethod = methodLine; + return createdByMethod; + } + } + + return null; + } + + private boolean skipElement(String methodLine) { + if (methodLine.startsWith("java.lang.")) { + return true; + } else if (methodLine.startsWith("java.util.")) { + return true; + } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.CallableQuery.")) { + // creating connection on future... + return true; + } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.Callable")) { + // it is a future task being executed... + return false; + } else if (methodLine.startsWith("com.avaje.ebeaninternal")) { + return true; + } else { + return false; + } + } + + /** + * Set the stack trace to help find connection pool leaks. + */ + protected void setStackTrace(StackTraceElement[] stackTrace) { + this.stackTrace = stackTrace; + } + + /** + * Return the full stack trace that got the connection from the pool. You + * could use this if getCreatedByMethod() doesn't work for you. + */ + public StackTraceElement[] getStackTrace() { + + if (stackTrace == null){ + return null; + } + + // filter off the top of the stack that we are not interested in + ArrayList filteredList = new ArrayList(); + boolean include = false; + for (int i = 0; i < stackTrace.length; i++) { + if (!include && !skipElement(stackTrace[i].toString())){ + include = true; + } + if (include && filteredList.size() < maxStackTrace){ + filteredList.add(stackTrace[i]); + } + } + return filteredList.toArray(new StackTraceElement[filteredList.size()]); + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java new file mode 100644 index 000000000..bbc268a4f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java @@ -0,0 +1,606 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool.Status; + +public class PooledConnectionQueue { + + private static final Logger logger = Logger.getLogger(PooledConnectionQueue.class.getName()); + + private static final TimeUnit MILLIS_TIME_UNIT = TimeUnit.MILLISECONDS; + + private final String name; + + private final DataSourcePool pool; + + /** + * A 'circular' buffer designed specifically for free connections. + */ + private final FreeConnectionBuffer freeList; + + /** + * A 'slots' buffer designed specifically for busy connections. + * Fast add remove based on slot id. + */ + private final BusyConnectionBuffer busyList; + + /** + * Main lock guarding all access + */ + private final ReentrantLock lock; + + /** + * Condition for threads waiting to take a connection + */ + private final Condition notEmpty; + + private int connectionId; + + private long waitTimeoutMillis; + + private long leakTimeMinutes; + + private int warningSize; + + private int maxSize; + + private int minSize; + + /** + * Number of threads in the wait queue. + */ + private int waitingThreads; + + /** + * Number of times a thread had to wait. + */ + private int waitCount; + + /** + * Number of times a connection was got from this queue. + */ + private int hitCount; + + /** + * The high water mark for the queue size. + */ + private int highWaterMark; + + /** + * Last time the pool was reset. Used to close busy connections as they are + * returned to the pool that where created prior to the lastResetTime. + */ + private long lastResetTime; + + private boolean doingShutdown; + + public PooledConnectionQueue(DataSourcePool pool) { + + this.pool = pool; + this.name = pool.getName(); + this.minSize = pool.getMinSize(); + this.maxSize = pool.getMaxSize(); + + this.warningSize = pool.getWarningSize(); + this.waitTimeoutMillis = pool.getWaitTimeoutMillis(); + this.leakTimeMinutes = pool.getLeakTimeMinutes(); + + this.busyList = new BusyConnectionBuffer(50,20); + this.freeList = new FreeConnectionBuffer(maxSize); + + this.lock = new ReentrantLock(true); + this.notEmpty = lock.newCondition(); + } + + private Status createStatus() { + return new Status(name, minSize, maxSize, freeList.size(), busyList.size(), waitingThreads, highWaterMark, waitCount, hitCount); + } + + public String toString() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return createStatus().toString(); + } finally { + lock.unlock(); + } + } + + public Status getStatus(boolean reset) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Status s = createStatus(); + if (reset){ + highWaterMark = busyList.size(); + hitCount = 0; + waitCount = 0; + } + return s; + } finally { + lock.unlock(); + } + } + + public void setMinSize(int minSize) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + if (minSize > this.maxSize){ + throw new IllegalArgumentException("minSize "+minSize+" > maxSize "+this.maxSize); + } + this.minSize = minSize; + } finally { + lock.unlock(); + } + } + + public void setMaxSize(int maxSize) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + if (maxSize < this.minSize){ + throw new IllegalArgumentException("maxSize "+maxSize+" < minSize "+this.minSize); + } + freeList.setCapacity(maxSize); + this.maxSize = maxSize; + } finally { + lock.unlock(); + } + } + + public void setWarningSize(int warningSize) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + if (warningSize > this.maxSize){ + throw new IllegalArgumentException("warningSize "+warningSize+" > maxSize "+this.maxSize); + } + this.warningSize = warningSize; + } finally { + lock.unlock(); + } + } + + private int totalConnections() { + return freeList.size() + busyList.size(); + } + + public void ensureMinimumConnections() throws SQLException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + int add = minSize - totalConnections(); + if (add > 0){ + for (int i = 0; i < add; i++) { + PooledConnection c = pool.createConnectionForQueue(connectionId++); + freeList.add(c); + } + notEmpty.signal(); + } + + } finally { + lock.unlock(); + } + } + + /** + * Return a PooledConnection. + */ + protected void returnPooledConnection(PooledConnection c) { + + final ReentrantLock lock = this.lock; + lock.lock(); + try { + if (!busyList.remove(c)) { + logger.log(Level.SEVERE, "Connection [" + c + "] not found in BusyList? "); + } + if (c.getCreationTime() <= lastResetTime) { + c.closeConnectionFully(false); + } else { + freeList.add(c); + notEmpty.signal(); + } + } finally { + lock.unlock(); + } + } + + private PooledConnection extractFromFreeList() { + PooledConnection c = freeList.remove(); + registerBusyConnection(c); + return c; + } + + public PooledConnection getPooledConnection() throws SQLException { + + try { + PooledConnection pc = _getPooledConnection(); + pc.resetForUse(); + return pc; + + } catch (InterruptedException e) { + String msg = "Interrupted getting connection from pool "+e; + throw new SQLException(msg); + } + } + + /** + * Register the PooledConnection with the busyList. + */ + private int registerBusyConnection(PooledConnection c) { + int busySize = busyList.add(c); + if (busySize > highWaterMark){ + highWaterMark = busySize; + } + return busySize; + } + + private PooledConnection _getPooledConnection() throws InterruptedException, SQLException { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + if (doingShutdown) { + throw new SQLException("Trying to access the Connection Pool when it is shutting down"); + } + + // this includes attempts that fail with InterruptedException + // or SQLException but that is ok as its only an indicator + hitCount++; + + // are other threads already waiting? (they get priority) + if (waitingThreads == 0){ + + int freeSize = freeList.size(); + if (freeSize > 0){ + // we have a free connection to return + return extractFromFreeList(); + } + + if (busyList.size() < maxSize){ + // grow the connection pool + PooledConnection c = pool.createConnectionForQueue(connectionId++); + int busySize = registerBusyConnection(c); + + String msg = "DataSourcePool [" + name + "] grow; id["+c.getName()+"] busy["+busySize+"] max["+maxSize+"]"; + logger.info(msg); + + checkForWarningSize(); + return c; + } + } + + try { + // The pool is at maximum size. We are going to go into + // a wait loop until connections are returned into the pool. + waitCount++; + waitingThreads++; + return _getPooledConnectionWaitLoop(); + } finally { + waitingThreads--; + } + + } finally { + lock.unlock(); + } + } + + /** + * Got into a loop waiting for connections to be returned to the pool. + */ + private PooledConnection _getPooledConnectionWaitLoop() throws SQLException, InterruptedException { + + long nanos = MILLIS_TIME_UNIT.toNanos(waitTimeoutMillis); + for (;;) { + + if (nanos <= 0) { + String msg = "Unsuccessfully waited ["+waitTimeoutMillis+"] millis for a connection to be returned." + + " No connections are free. You need to Increase the max connections of ["+maxSize+"]" + + " or look for a connection pool leak using datasource.xxx.capturestacktrace=true"; + if (pool.isCaptureStackTrace()) { + dumpBusyConnectionInformation(); + } + + throw new SQLException(msg); + } + + try { + nanos = notEmpty.awaitNanos(nanos); + if (!freeList.isEmpty()) { + // successfully waited + return extractFromFreeList(); + } + } catch (InterruptedException ie) { + notEmpty.signal(); // propagate to non-interrupted thread + throw ie; + } + } + } + + public void shutdown() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + doingShutdown = true; + Status status = createStatus(); + logger.info("DataSourcePool [" + name + "] shutdown: "+status); + + closeFreeConnections(true); + + if (!busyList.isEmpty()) { + String msg = "A potential connection leak was detected. Busy connections: "+ busyList.size(); + logger.warning(msg); + + dumpBusyConnectionInformation(); + closeBusyConnections(0); + } + } finally { + lock.unlock(); + } + } + + /** + * Close all the connections in the pool and any current busy connections + * when they are returned. New connections will be then created on demand. + *

+ * This is typically done when a database down event occurs. + *

+ */ + public void reset(long leakTimeMinutes) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Status status = createStatus(); + logger.info("Reseting DataSourcePool [" + name + "] "+status); + lastResetTime = System.currentTimeMillis(); + + closeFreeConnections(false); + closeBusyConnections(leakTimeMinutes); + + String busyMsg = "Busy Connections:\r\n" + getBusyConnectionInformation(); + logger.info(busyMsg); + + } finally { + lock.unlock(); + } + } + + public void trim(int maxInactiveTimeSecs) throws SQLException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + trimInactiveConnections(maxInactiveTimeSecs); + ensureMinimumConnections(); + + } finally { + lock.unlock(); + } + } + + /** + * Trim connections that have been not used for some time. + */ + private int trimInactiveConnections(int maxInactiveTimeSecs) { + + int maxTrim = freeList.size() - minSize; + if (maxTrim <= 0) { + return 0; + } + + int trimedCount = 0; + long usedSince = System.currentTimeMillis() - (maxInactiveTimeSecs * 1000); + + // get a shallow copy to manipulate + List freeListCopy = freeList.getShallowCopy(); + + Iterator it = freeListCopy.iterator(); + while (it.hasNext()) { + PooledConnection pc = it.next(); + if (pc.getLastUsedTime() < usedSince) { + // trim this connection as it hasn't been used in a while + trimedCount++; + it.remove(); + pc.closeConnectionFully(true); + if (trimedCount >= maxTrim) { + break; + } + } + } + + if (trimedCount > 0) { + + // rebuild the free list from the trimmed copy + freeList.setShallowCopy(freeListCopy); + + String msg = "DataSourcePool [" + name + "] trimmed [" + trimedCount + "] inactive connections. New size[" + totalConnections() + "]"; + logger.info(msg); + } + return trimedCount; + } + + /** + * Close all the connections that are in the free list. + */ + public void closeFreeConnections(boolean logErrors) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + while (!freeList.isEmpty()) { + PooledConnection c = freeList.remove(); + logger.info("PSTMT Statistics: "+c.getStatistics()); + c.closeConnectionFully(logErrors); + } + } finally { + lock.unlock(); + } + } + + /** + * Close any busy connections that have not been used for some time. + *

+ * These connections are considered to have leaked from the connection pool. + *

+ *

+ * Connection leaks occur when code doesn't ensure that connections are + * closed() after they have been finished with. There should be an + * appropriate try catch finally block to ensure connections are always + * closed and put back into the pool. + *

+ */ + public void closeBusyConnections(long leakTimeMinutes) { + + final ReentrantLock lock = this.lock; + lock.lock(); + try { + + long olderThanTime = System.currentTimeMillis() - (leakTimeMinutes*60000); + + List copy = busyList.getShallowCopy(); + for (int i = 0; i < copy.size(); i++) { + PooledConnection pc = copy.get(i); + if (pc.isLongRunning() || pc.getLastUsedTime() > olderThanTime) { + // PooledConnection has been used recently or + // expected to be longRunning so not closing... + } else { + busyList.remove(pc); + closeBusyConnection(pc); + } + } + + } finally { + lock.unlock(); + } + } + + private void closeBusyConnection(PooledConnection pc) { + try { + String methodLine = pc.getCreatedByMethod(); + + Date luDate = new Date(); + luDate.setTime(pc.getLastUsedTime()); + + String msg = "DataSourcePool closing leaked connection? " + " name[" + + pc.getName() + "] lastUsed[" + luDate + "] createdBy[" + methodLine + + "] lastStmt[" + pc.getLastStatement() + "]"; + + logger.warning(msg); + logStackElement(pc, "Possible Leaked Connection: "); + + System.out.println("CLOSING BUSY CONNECTION ??? "+pc); + pc.close(); + + } catch (SQLException ex) { + // this should never actually happen + logger.log(Level.SEVERE, null, ex); + } + } + + private void logStackElement(PooledConnection pc, String prefix) { + StackTraceElement[] stackTrace = pc.getStackTrace(); + if (stackTrace != null){ + String s = Arrays.toString(stackTrace); + String msg = prefix+" name["+pc.getName()+"] stackTrace: "+s; + logger.warning(msg); + // also send to syserr ... as the loggers get turned + // off early in JVM shutdown + System.err.println(msg); + } + } + + + /** + * As the pool grows it gets closer to the maxConnections limit. We can send + * an Alert (or warning) as we get close to this limit and hence an + * Administrator could increase the pool size if desired. + *

+ * This is called whenever the pool grows in size (towards the max limit). + *

+ */ + private void checkForWarningSize() { + + // the the total number of connections that we can add + // to the pool before it hits the maximum + int availableGrowth = (maxSize - totalConnections()); + + if (availableGrowth < warningSize) { + + closeBusyConnections(leakTimeMinutes); + + String msg = "DataSourcePool [" + name + "] is [" + availableGrowth+ "] connections from its maximum size."; + pool.notifyWarning(msg); + } + } + + public String getBusyConnectionInformation() { + return getBusyConnectionInformation(false); + } + + public void dumpBusyConnectionInformation() { + getBusyConnectionInformation(true); + } + + /** + * Returns information describing connections that are currently being used. + */ + private String getBusyConnectionInformation(boolean toLogger) { + + final ReentrantLock lock = this.lock; + lock.lock(); + try { + + if (toLogger) { + logger.info("Dumping busy connections: (Use datasource.xxx.capturestacktrace=true ... to get stackTraces)"); + } + + StringBuilder sb = new StringBuilder(); + + List copy = busyList.getShallowCopy(); + for (int i = 0; i < copy.size(); i++) { + PooledConnection pc = copy.get(i); + if (toLogger) { + logger.info(pc.getDescription()); + logStackElement(pc, "Busy Connection: "); + + } else { + sb.append(pc.getDescription()).append("\r\n"); + } + } + + return sb.toString(); + + } finally { + lock.unlock(); + } + } + +} + diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java new file mode 100644 index 000000000..7433aff54 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.util.Random; +import java.util.logging.Logger; + +/** + * Security mechanisim. + */ +public class Prefix { + + private static final Logger logger = Logger.getLogger(Prefix.class.getName()); + + private static final int[] oa = { 50, 12, 4, 6, 8, 10, 7, 23, 45, 23, 6, 9, 12, 2, 8, 34 }; + + public static String getProp(String prop) { + String v = dec(prop); + int p = v.indexOf(":"); + String r = v.substring(1, p); + return r; + } + + public static void main(String[] args) { + String m = e(args[0]); + logger.info("[" + m + "]"); + String o = getProp(m); + logger.info("[" + o + "]"); + } + + public static String e(String msg) { + msg = elen(msg, 40); + return enc(msg); + } + + public static byte az(byte c, int offset) { + + int z = c + offset; + if (z > 122) { + // dp("z> "+z); + z = z - 122 + 48 - 1; + } + // dp("z="+z+" c:"+(int)c); + return (byte) z; + } + + public static byte bz(byte c, int offset) { + int z = c - offset; + if (z < (48)) { + // dp("z< "+z); + z = z + 122 - 48 + 1; + } + return (byte) z; + } + + public static String enc(String msg) { + byte[] msgbytes = msg.getBytes(); + byte[] encbytes = new byte[msgbytes.length + 1]; + Random r = new Random(); + int key = r.nextInt(70); + + char k = (char) (key + 48); + + encbytes[0] = az((byte) k, oa[0]); + // dp("key:"+key+" encbytes[0]:"+(byte)encbytes[0]); + int ios = key; + for (int i = 1; i < (msgbytes.length + 1); i++) { + encbytes[i] = az(msgbytes[i - 1], (oa[(i + ios) % oa.length])); + } + return new String(encbytes); + } + + public static String dec(String msg) { + byte[] msgbytes = msg.getBytes(); + byte[] encbytes = new byte[msgbytes.length]; + + encbytes[0] = bz(msgbytes[0], oa[0]); + byte key = encbytes[0]; + int ios = (key - 48); + for (int i = 1; i < msgbytes.length; i++) { + encbytes[i] = bz(msgbytes[i], oa[(i + ios) % oa.length]); + } + return new String(encbytes); + } + + public static String elen(String msg, int len) { + Random r = new Random(); + if (msg.length() < len) { + int max = len - msg.length(); + StringBuilder sb = new StringBuilder(); + sb.append(msg).append(":"); + for (int i = 1; i < max; i++) { + int bc = r.nextInt(122 - 48); + sb.append(Character.toString((char) (bc + 48))); + } + return sb.toString(); + } + return msg; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java new file mode 100644 index 000000000..2eaea32c9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java @@ -0,0 +1,181 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A LRU based cache for PreparedStatements. + */ +public class PstmtCache extends LinkedHashMap { + + private static final Logger logger = Logger.getLogger(PstmtCache.class.getName()); + + static final long serialVersionUID = -3096406924865550697L; + + /** + * The name of the cache, for tracing purposes. + */ + final String cacheName; + + /** + * The maximum size of the cache. When this is exceeded the oldest entry is removed. + */ + final int maxSize; + + /** + * The total number of entries removed from this cache. + */ + int removeCounter; + + /** + * The number of get hits. + */ + int hitCounter; + + /** + * The number of get() misses. + */ + int missCounter; + + /** + * The number of puts into this cache. + */ + int putCounter; + + public PstmtCache(String cacheName, int maxCacheSize) { + + // note = access ordered list. This is what gives it the LRU order + super(maxCacheSize*3, 0.75f, true); + this.cacheName = cacheName; + this.maxSize = maxCacheSize; + } + + /** + * Return a summary description of this cache. + */ + public String getDescription() { + return cacheName+" size:"+size()+" max:"+maxSize+" totalHits:"+hitCounter+" hitRatio:"+getHitRatio()+" removes:"+removeCounter; + } + + /** + * returns the current maximum size of the cache. + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Gets the hit ratio. A number between 0 and 100 indicating the number of + * hits to misses. A number approaching 100 is desirable. + */ + public int getHitRatio() { + if (hitCounter == 0) { + return 0; + } else { + return hitCounter*100/(hitCounter+missCounter); + } + } + + /** + * The total number of hits against this cache. + */ + public int getHitCounter() { + return hitCounter; + } + + /** + * The total number of misses against this cache. + */ + public int getMissCounter() { + return missCounter; + } + + /** + * The total number of puts against this cache. + */ + public int getPutCounter() { + return putCounter; + } + + /** + * additionally maintains hit and miss statistics. + */ + public ExtendedPreparedStatement get(Object key) { + + ExtendedPreparedStatement o = super.get(key); + if (o == null) { + missCounter++; + } else { + hitCounter++; + } + return o; + } + + /** + * additionally maintains hit and miss statistics. + */ + public ExtendedPreparedStatement remove(Object key) { + + ExtendedPreparedStatement o = super.remove(key); + if (o == null) { + missCounter++; + } else { + hitCounter++; + } + return o; + } + + /** + * additionally maintains put counter statistics. + */ + public ExtendedPreparedStatement put(String key, ExtendedPreparedStatement value) { + + putCounter++; + return super.put(key, value); + } + + /** + * will check to see if we need to remove entries and + * if so call the cacheCleanup.cleanupEldestLRUCacheEntry() if + * one has been set. + */ + protected boolean removeEldestEntry(Map.Entry eldest) { + + if (size() < maxSize) { + return false; + } + + removeCounter++; + + try { + ExtendedPreparedStatement pstmt = eldest.getValue(); + pstmt.closeDestroy(); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Error closing ExtendedPreparedStatement", e); + } + return true; + } + + +} + diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleAlerter.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleAlerter.java new file mode 100644 index 000000000..3f610c26a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleAlerter.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.server.lib.util.MailEvent; +import com.avaje.ebeaninternal.server.lib.util.MailListener; +import com.avaje.ebeaninternal.server.lib.util.MailMessage; +import com.avaje.ebeaninternal.server.lib.util.MailSender; + +/** + * A simple smtp email alert that sends a email message + * on dataSourceDown and dataSourceUp etc. + *
    + *
  • alert.fromuser = the from user name + *
  • alert.fromemail = the from email account + *
  • alert.toemail = comma delimited list of email accounts to email + *
  • alert.mailserver = the smpt server name + *
+ */ +public class SimpleAlerter implements DataSourceAlertListener, MailListener { + + private static final Logger logger = Logger.getLogger(SimpleAlerter.class.getName()); + + //boolean sendInBackGround = true; + + /** + * Create a SimpleAlerter. + */ + public SimpleAlerter() { + } + + /** + * If the email failed then log the error. + */ + public void handleEvent(MailEvent event) { + Throwable e = event.getError(); + if (e != null){ + logger.log(Level.SEVERE, null, e); + } + } + + /** + * Send the dataSource down alert. + */ + public void dataSourceDown(String dataSourceName) { + String msg = getSubject(true, dataSourceName); + sendMessage(msg, msg); + } + + /** + * Send the dataSource up alert. + */ + public void dataSourceUp(String dataSourceName) { + String msg = getSubject(false, dataSourceName); + sendMessage(msg, msg); + } + + /** + * Send the warning message. + */ + public void warning(String subject, String msg) { + sendMessage(subject, msg); + } + + private String getSubject(boolean isDown, String dsName) { + String msg = "The DataSource "+dsName; + if (isDown){ + msg += " is DOWN!!"; + } else { + msg += " is UP."; + } + return msg; + } + + private void sendMessage(String subject, String msg){ + + String fromUser = GlobalProperties.get("alert.fromuser", null); + String fromEmail = GlobalProperties.get("alert.fromemail", null); + String mailServerName = GlobalProperties.get("alert.mailserver", null); + String toEmail = GlobalProperties.get("alert.toemail", null); + + if (mailServerName == null){ + //throw new RuntimeException("alert.mailserver not set..."); + return; + } + + MailMessage data = new MailMessage(); + data.setSender(fromUser, fromEmail); + data.addBodyLine(msg); + data.setSubject(subject); + + String[] toList = toEmail.split(","); + if (toList.length==0) { + throw new RuntimeException("alert.toemail has not been set?"); + } + for (int i = 0; i < toList.length; i++) { + data.addRecipient(null, toList[i].trim()); + } + + MailSender sender = new MailSender(mailServerName); + sender.setMailListener(this); + sender.sendInBackground(data); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java new file mode 100644 index 000000000..731b2d2b5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.sql; + +import java.sql.Connection; + +/** + * Helper object that can convert between transaction isolation descriptions and values. + * + */ +public class TransactionIsolation { + + + /** + * return the isolation level for a given string description. + */ + public static int getLevel(String level) { + level = level.toUpperCase(); + if (level.startsWith("TRANSACTION")){ + level = level.substring("TRANSACTION".length()); + } + level = level.replace("_", ""); + if ("NONE".equalsIgnoreCase(level)){ + return Connection.TRANSACTION_NONE; + } + if ("READCOMMITTED".equalsIgnoreCase(level)){ + return Connection.TRANSACTION_READ_COMMITTED; + } + if ("READUNCOMMITTED".equalsIgnoreCase(level)){ + return Connection.TRANSACTION_READ_UNCOMMITTED; + } + if ("REPEATABLEREAD".equalsIgnoreCase(level)){ + return Connection.TRANSACTION_REPEATABLE_READ; + } + if ("SERIALIZABLE".equalsIgnoreCase(level)){ + return Connection.TRANSACTION_SERIALIZABLE; + } + + throw new RuntimeException("Transaction Isolaction level [" + level + "] is not known."); + } + + /** + * Return the string description of the transaction isolation level specified. + *

Returned value is one of NONE, READ_COMMITTED,READ_UNCOMMITTED, + * REPEATABLE_READ or SERIALIZABLE.

+ * + * @param level the transaction isolation level as per java.sql.Connection + * @return the level description as a string. + */ + public static String getLevelDescription(int level) { + switch (level) { + case Connection.TRANSACTION_NONE : + return "NONE"; + case Connection.TRANSACTION_READ_COMMITTED : + return "READ_COMMITTED"; + case Connection.TRANSACTION_READ_UNCOMMITTED : + return "READ_UNCOMMITTED"; + case Connection.TRANSACTION_REPEATABLE_READ : + return "REPEATABLE_READ"; + case Connection.TRANSACTION_SERIALIZABLE : + return "SERIALIZABLE"; + case -1 : + return "NotSet"; + default : + throw new RuntimeException("Transaction Isolaction level [" + level + "] is not defined."); + } + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/message.properties b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/message.properties new file mode 100644 index 000000000..8fe526f32 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/message.properties @@ -0,0 +1,20 @@ +FATAL_ERROR=ERROR: A fatal error has occured. Check the error log. {0} +DATASOURCE_OK=DataSource {0} working ok. +SHUTTING_DOWN=DataSource shutdown all datasources in [{0}] +SHUT_DOWN_FINISHED=DataSource shutdown has finished. +CANT_FIND_PROPS=ERROR: Can't find the datasource props file. +ERROR_CREATING_FACTORY=ERROR: An error occured when creating the DataSourceFactory {0}. +CANT_SHUTDOWN_TYPE=WARN: Can't shutdown DataSource objects of this type {0}. + +DB_DRIVER_NOTFOUND=ERROR: The JDBC Driver {0} can't be found. +POOL_IN_SHUTDOWN=ERROR: Trying to use the pool while it is shutting down. +WAIT_TIME_EXCEEDED=ERROR: Wait time {0} for connection exceeded. {1}. +SHUTDOWN_START=DataSource [{0}] Shutting down. +SHUTDOWN_LEAK=A Connection leak has been detected on shutdown {0}. +SHUTDOWN_END=DataSource [{0}] Shutdown ended. +METHOD_NOT_SUPPORTED=ERROR: this method is not supported. +SET_ALERT=WARN: Alert {0} has been set. +MISSING_PARAMETER=ERROR: A parameter {0} is missing from the props file. +IDLE_CONNECTION_ACCESSED=Pooled Connection has been accessed whilst idle in the pool, via method: + +DEFAULT_DS_NOT_SPECIFIED=ERROR: No default dataSource has been specified. diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/package.html b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/package.html new file mode 100644 index 000000000..4f39901a2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/package.html @@ -0,0 +1,12 @@ + + + + AvajeLib + + +Enhanced JDBC objects and connection pool. + +

Provides Database meta data objects, Robust connection pooling, PreparedStatement Caching. +

+ + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/thread/PooledThread.java b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/PooledThread.java new file mode 100644 index 000000000..2b6878038 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/PooledThread.java @@ -0,0 +1,276 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.thread; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A thread that belongs to a ThreadPool. It will return to the Threadpool when + * it has finished its assigned task. + */ +public class PooledThread implements Runnable { + + private static final Logger logger = Logger.getLogger(PooledThread.class.getName()); + + /** + * Create the PooledThread. + */ + protected PooledThread(ThreadPool threadPool, String name, boolean isDaemon, + Integer threadPriority) { + + this.name = name; + this.threadPool = threadPool; + this.lastUsedTime = System.currentTimeMillis(); + + thread = new Thread(this, name); + thread.setDaemon(isDaemon); + + if (threadPriority != null) { + thread.setPriority(threadPriority.intValue()); + } + //thread.start(); + } + + protected void start() { + thread.start(); + } + + /** + * Assign work to this thread. The thread will notify the listener when it + * has finished the work. + */ + public boolean assignWork(Work work) { + synchronized (workMonitor) { + this.work = work; + workMonitor.notifyAll(); + } + return true; + } + + /** + * process any assigned work until stopped or interrupted. + */ + public void run() { + // process assigned work until we receive a shutdown signal + synchronized (workMonitor) { + while (!isStopping) { + try { + if (work == null) { + workMonitor.wait(); + } + } catch (InterruptedException e) { + } + doTheWork(); + } + } + + // Tell stop() we have shut ourselves down successfully + synchronized (threadMonitor) { + threadMonitor.notifyAll(); + } + //Log.debug("PooledThread [" + getName() + "] finished "); + isStopped = true; + } + + /** + * Actually do the work and gather the appropriate measures. + */ + private void doTheWork() { + if (isStopping){ + return; + } + + long startTime = System.currentTimeMillis(); + if (work == null) { + // probably shutting down the thread + + } else { + try { + work.setStartTime(startTime); + work.getRunnable().run(); + + } catch (Throwable ex) { + logger.log(Level.SEVERE, null, ex); + + if (wasInterrupted) { + this.isStopping = true; + threadPool.removeThread(this); + logger.info("PooledThread [" + name + "] removed due to interrupt"); + try { + thread.interrupt(); + } catch (Exception e){ + String msg = "Error interrupting PooledThead["+name+"]"; + logger.log(Level.SEVERE, msg, e); + } + return; + } + } + } + lastUsedTime = System.currentTimeMillis(); + totalWorkCount++; + totalWorkExecutionTime = totalWorkExecutionTime + lastUsedTime - startTime; + this.work = null; + threadPool.returnThread(this); + + } + + /** + * Try to interrupt the thread. + *

+ * If the Thread was interrupted then it will be removed from the pool. + *

+ */ + public void interrupt() { + + // set a flag so doTheWork knows that it was interrupted + // and removes rather than returns + wasInterrupted = true; + try { + thread.interrupt(); + + } catch (SecurityException ex) { + wasInterrupted = false; + throw ex; + } + } + + /** + * Returns true if the thread has finished. + */ + public boolean isStopped() { + return isStopped; + } + + /** + * Stop the thread relatively nicely. It will wait a maximum of 10 seconds + * for it to complete any existing work. + */ + protected void stop() { + isStopping = true; + + synchronized (threadMonitor) { + + assignWork(null); + //trace("stop assigned null work..."); + try { + threadMonitor.wait(10000); + } catch (InterruptedException e) { + ; + } + + } + + thread = null; + threadPool.removeThread(this); + } + + /** + * return the name of the thread. + */ + public String getName() { + return name; + } + /** + * Returns the currently executing work, otherwise null. + */ + public Work getWork() { + return work; + } + + /** + * The total number of jobs this thread has run. + */ + public int getTotalWorkCount() { + return totalWorkCount; + } + + /** + * The total time for performing all assigned work. + */ + public long getTotalWorkExecutionTime() { + return totalWorkExecutionTime; + } + + /** + * Returns the time this thread was last used. + */ + public long getLastUsedTime() { + return lastUsedTime; + } + + /** + * Flag to indicate that the thread was interrupted. + */ + private boolean wasInterrupted = false; + + /** + * The time the thread was last used. + */ + private long lastUsedTime; + + /** + * The work to run + */ + private Work work = null; + + /** + * Set to indicate the thread is stopping. + */ + private boolean isStopping = false; + + /** + * Set when the thread has stopped. + */ + private boolean isStopped = false; + /** + * The background thread. + */ + private Thread thread = null; + + /** + * The pool this worker belongs to. + */ + private ThreadPool threadPool; + + /** + * The name of the Thread + */ + private String name = null; + + /** + * The thread synchronization object. + */ + private Object threadMonitor = new Object(); + + /** + * The monitor for work notification. + */ + private Object workMonitor = new Object(); + + /** + * Total number of work performed. + */ + private int totalWorkCount = 0; + + /** + * Total work execution time. + */ + private long totalWorkExecutionTime = 0; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPool.java new file mode 100644 index 000000000..ef0ae72e5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPool.java @@ -0,0 +1,489 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.thread; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is a pool of threads which can be assigned work. + *

+ * The Pool will automatically grow as required up to its maximum pool size. The + * Pool will be automatically shrink by trimming threads that have been idle for + * some time. + *

+ */ +public class ThreadPool { + + private static final Logger logger = Logger.getLogger(ThreadPool.class.getName()); + + /** + * The max idle time used to trim idle threads from the pool. + */ + private long maxIdleTime; + + /** + * The name of the pool + */ + private String poolName; + + /** + * The initial pool size. + */ + private int minSize; + + /** + * Whether or not the threads are going to be Daemon threads. + */ + private boolean isDaemon; + + /** + * Flag to indicate that the pool is being shutdown. + */ + private boolean isStopping = false; + + /** + * The priority or the threads. Can be null, in which case the threads have + * the default priority. + */ + private Integer threadPriority; + + /** + * Incrementing int for thread name. NB: currentThreadCount will go up and + * down as the pool grows and shrinks. + */ + private int uniqueThreadID; + + /** + * List of PooledThread that are free for work. + */ + private Vector freeList = new Vector(); + + /** + * List of PooledThread that are busy. + */ + private Vector busyList = new Vector(); + + /** + * List holding queued work. + */ + private Vector workOverflowQueue = new Vector(); + + /** + * The maximum number of threads to grow to. Hitting this limit will have + * performance ramifications. + */ + private int maxSize = 100; + + /** + * Flag that the pool should terminate all the threads and stop. + */ + private boolean stopThePool; + + + /** + * Create the ThreadPool. + */ + public ThreadPool(String poolName, boolean isDaemon, Integer threadPriority) { + + this.poolName = poolName; + this.stopThePool = false; + this.isDaemon = isDaemon; + this.threadPriority = threadPriority; + } + + /** + * Return true if the pool is shutting down. + */ + public boolean isStopping() { + return isStopping; + } + + /** + * Return the name of the thread pool. + */ + public String getName() { + return poolName; + } + + /** + * Set the minimum size the pool should try to maintain. + */ + public void setMinSize(int minSize) { + if (minSize > 0) { + if (minSize > maxSize) { + this.maxSize = minSize; + } + this.minSize = minSize; + maintainPoolSize(); + } + } + + /** + * Return the minimum size the pool should maintain. + */ + public int getMinSize() { + return minSize; + } + + /** + * Set the maximum size the pool should grow to. + */ + public void setMaxSize(int maxSize) { + if (maxSize > 0) { + if (minSize > maxSize) { + minSize = maxSize; + } + this.maxSize = maxSize; + maintainPoolSize(); + } + } + + /** + * Return the maximum size this pool can grow to. + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Return the total number of busy and free threads in the pool. + */ + public int size() { + return busyList.size() + freeList.size(); + } + + /** + * Return the number of currently busy threads. + */ + public int getBusyCount() { + return busyList.size(); + } + + /** + * Assign a task to the thread pool, specifing the options to wait or queue + * the task if the pool is fully busy and can't grow. + *

+ * When the pool is fully busy... + *

+ *

+ * addToQueue=true -> work is added to queue, returns false
+ * addToQueue=false -> work is not done or queued, returns false
+ *

+ * + * @param work the runnable work to do. + * @param addToQueueIfFull If the pool is maxed out and this is true then it + * queues the Runnable. + */ + public boolean assign(Runnable work, boolean addToQueueIfFull) { + + if (stopThePool) { + throw new RuntimeException("Pool is stopping... no more work please."); + } + + Work runWork = new Work(work); + + // get the next available thread in the pool (block) + PooledThread thread = getNextAvailableThread(); + if (thread != null) { + // assign the work to that thread + busyList.add(thread); + thread.assignWork(runWork); + return true; + + } else { + if (addToQueueIfFull) { + runWork.setEnterQueueTime(System.currentTimeMillis()); + workOverflowQueue.add(runWork); + } + return false; + } + } + + /** + * Remove the thread from the pool. The thread should be stopped before it + * is removed. + */ + protected void removeThread(PooledThread thread) { + synchronized (freeList) { + busyList.remove(thread); + freeList.remove(thread); + freeList.notify(); + + // if (ThreadPoolManager.getDebugLevel()>0){ + //Log.debug("PooledThread stopped [" + getName() + "]"); + // } + } + } + + /** + * fired when a Thread from the pool has finished, and can be put back into + * the pool. + */ + protected void returnThread(PooledThread thread) { + + synchronized (freeList) { + + // deregister from the busyList + busyList.remove(thread); + + if (!workOverflowQueue.isEmpty()) { + // get the first bit of work off the queue + Work queuedWork = (Work) workOverflowQueue.remove(0); + + // work out the queue time and counts etc + queuedWork.setExitQueueTime(System.currentTimeMillis()); + busyList.add(thread); + thread.assignWork(queuedWork); + + } else { + // put the thread back onto the available list + freeList.add(thread); + // tell shutdown() one has returned + freeList.notify(); + } + } + } + + + /** + * Get the next available thread. Block until thread is available. NB: The + * dispatcher is blocked but work can still be assigned to the dispatcher in + * a non-blocking way + */ + private PooledThread getNextAvailableThread() { + + synchronized (freeList) { + if (!freeList.isEmpty()) { + return (PooledThread) freeList.remove(0); + } + if (size() < maxSize) { + return growPool(true); + } + return null; + } + } + + /** + * Return an Iterator of PooledThread that are currently running. You should + * only use this for display. Use the getPooledThread() or interrupt() + * methods to interrupt a particular thread. + * + * @return an Iterator of busy PooledThread's. + */ + public Iterator getBusyThreads() { + synchronized (freeList) { + return busyList.iterator(); + } + } + + /** + * Shutdown the threadpool stopping all the threads. This will + * wait until any busy threads have finished their assigned work. + */ + protected void shutdown() { + + synchronized (freeList) { + isStopping = true; + + int size = size(); + + if (size > 0){ + String msg = null; + msg = "ThreadPool [" + poolName + "] Shutting down; threadCount[" + size() + + "] busyCount[" + getBusyCount() + "]"; + + logger.info(msg); + } + + stopThePool = true; + + while (!freeList.isEmpty()) { + PooledThread thread = (PooledThread) freeList.remove(0); + thread.stop(); + } + + try { + while (getBusyCount() > 0) { + // synchronized (freeList) { + String msg = "ThreadPool [" + poolName + "] has [" + getBusyCount() + + "] busy threads, waiting for those to finish."; + logger.info(msg); + + Iterator busyThreads = getBusyThreads(); + while (busyThreads.hasNext()) { + PooledThread busyThread = (PooledThread) busyThreads.next(); + + String threadName = busyThread.getName(); + Work work = busyThread.getWork(); + + String busymsg = "Busy thread [" + threadName + "] work[" + work + "]"; + logger.info(busymsg); + } + // trace("wait for a busy thread to be put back into + // freeList"); + freeList.wait(); + PooledThread thread = (PooledThread) freeList.remove(0); + // trace("wait finished...now shut it down [" + + // thread.getName() + "]"); + if (thread != null) { + thread.stop(); + } + } + + + } catch (InterruptedException e) { + logger.log(Level.SEVERE, null, e); + } + } + } + + /** + * Trim or grow the pool leaving at least min free. + */ + protected void maintainPoolSize() { + synchronized (freeList) { + if (isStopping) { + // don't bother as the pool is shutting down + return; + } + + int numToStop = size() - minSize; + if (numToStop > 0) { + // should trim idle threads as we are over the minSize + long usedAfter = System.currentTimeMillis() - maxIdleTime; + ArrayList stopList = new ArrayList(); + Iterator it = freeList.iterator(); + while (it.hasNext() && numToStop > 0) { + PooledThread thread = (PooledThread) it.next(); + if (thread.getLastUsedTime() < usedAfter) { + stopList.add(thread); + numToStop--; + } + } + Iterator stopIt = stopList.iterator(); + while (stopIt.hasNext()) { + PooledThread thread = (PooledThread) stopIt.next(); + thread.stop(); + } + } + int numToAdd = minSize - size(); + if (numToAdd > 0) { + // should add some more to the pool + for (int i = 0; i < numToAdd; i++) { + growPool(false); + } + } + } + } + + /** + * Interrupt a named thread that is currently busy. + *

+ * Returns the thread that was interrupted or null if the thread + * was not found. If the thread was interrupted then it will + * automatically be stopped and removed from the pool. + *

+ *

+ * Note that it may take some time to actually interrupt the thread so + * an immediate test to see if the thread stopped will probably be wrong. + *


+     * ThreadPool test = ThreadPoolManager.getThreadPool("test");
+     * PooledThread pt = test.interrupt("test.1");
+     * if (pt == null) {
+     *      // the thread was not found, perhaps finished?
+     * } else {
+     *      // give interrupt a little time to execute
+     *      Thread.sleep(1000);
+     *      boolean hasStopped = pt.isStopped();
+     *      //..
+     * }
+     * 
+ *

+ * @return the thread that was interrupted + */ + public PooledThread interrupt(String threadName) { + PooledThread thread = getBusyThread(threadName); + if (thread != null) { + thread.interrupt(); + return thread; + } + return null; + } + + /** + * Find a thread using its name from the busy list. Returns null if the + * thread is not found in the busy list. + */ + public PooledThread getBusyThread(String threadName) { + synchronized (freeList) { + Iterator it = getBusyThreads(); + while (it.hasNext()) { + PooledThread pt = (PooledThread) it.next(); + if (pt.getName().equals(threadName)) { + return pt; + } + } + return null; + } + } + + /** + * Grow the pool with the option of either putting it on the available list, + * or returning it. + */ + private PooledThread growPool(boolean andReturn) { + + synchronized (freeList) { + + String threadName = poolName + "." + uniqueThreadID++; + PooledThread bgw = new PooledThread(this, threadName, isDaemon, threadPriority); + bgw.start(); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("ThreadPool grow created [" + threadName + "] size[" + size() + "]"); + } + if (andReturn) { + return bgw; + } else { + freeList.add(bgw); + return null; + } + } + } + + /** + * Return the maximum amount of time in millis that Threads can be idle + * before they are trimmed. + */ + public long getMaxIdleTime() { + return maxIdleTime; + } + + /** + * Set the maxiumium amount of time in millis that Threads can be idle + * before they are trimed. + */ + public void setMaxIdleTime(long maxIdleTime) { + this.maxIdleTime = maxIdleTime; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPoolManager.java b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPoolManager.java new file mode 100644 index 000000000..cc3915ec1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/ThreadPoolManager.java @@ -0,0 +1,194 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.thread; + +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.server.lib.BackgroundThread; + +/** + * Singleton that manages a list of named ThreadPools. + */ +public class ThreadPoolManager implements Runnable { + + private static final class Single { + private static final ThreadPoolManager me = new ThreadPoolManager(); + } + + private static int debugLevel = 0; + + /** + * set when the pools are being shutdown. + */ + private boolean isShuttingDown = false; + + /** + * Holds all the thread pools. + */ + private ConcurrentHashMap threadPoolCache = new ConcurrentHashMap(); + + /** + * The default time threads are idle before they are stopped and removed. + * This can occur when the pool grows larger than the min size and then goes idle + * for some time. + */ + private long defaultIdleTime; + + private ThreadPoolManager() { + initialise(); + } + + private void initialise() { + + debugLevel = GlobalProperties.getInt("threadpool.debugLevel", 0); + + defaultIdleTime = 1000 * GlobalProperties.getInt("threadpool.idletime", 60); + + int freqIsSecs = GlobalProperties.getInt("threadpool.sleeptime", 30); + + BackgroundThread.add(freqIsSecs, this); + } + + /** + * Set the debug level. + */ + public static void setDebugLevel(int level) { + debugLevel = level; + } + + /** + * Return the debug level. + */ + public static int getDebugLevel() { + return debugLevel; + } + + /** + * Periodically maintains the pool size. Stops threads that have + * been idle for too long and ensures the minimum number of threads. + *

+ * To change this you can set the threadpool.idletime property:
+ *
+ * ## set threadpool idletime to 120 seconds
+ * threadpool.idletime=120
+ *

+ */ + public void run() { + if (!isShuttingDown) { + maintainPoolSize(); + } + } + + /** + * Return the named thread pool. + */ + public static ThreadPool getThreadPool(String poolName) { + return Single.me.getPool(poolName); + } + + /** + * Return the named ThreadPool. If the ThreadPool doesn't exist it will be + * created. + */ + private ThreadPool getPool(String poolName) { + synchronized (this) { + ThreadPool threadPool = (ThreadPool) threadPoolCache.get(poolName); + if (threadPool == null) { + threadPool = createThreadPool(poolName); + threadPoolCache.put(poolName, threadPool); + } + return threadPool; + } + } + + /** + * Returns an iterator of ThreadPools. + *

+ * Note that the ThreadPools should not be removed by the iterator. + *

+ */ + public static Iterator pools() { + return Single.me.threadPoolCache.values().iterator(); + } + + /** + * Maintain the size of all the thread pools. + * Trims down to minimum size threads that have been idle for a while. + * Adds threads if it is short of the minimum size. + */ + private void maintainPoolSize() { + if (isShuttingDown){ + return; + } + synchronized (this) { + + Iterator e = pools(); + while (e.hasNext()) { + ThreadPool pool = (ThreadPool) e.next(); + pool.maintainPoolSize(); + } + } + } + + /** + * Shutdown all the ThreadPools nicely. + * This will wait for all currently runnable and queued work to finish. + */ + public static void shutdown() { + Single.me.shutdownPools(); + } + + private void shutdownPools() { + synchronized (this) { + isShuttingDown = true; + Iterator i = pools(); + while (i.hasNext()) { + ThreadPool pool = (ThreadPool) i.next(); + pool.shutdown(); + } + } + } + + private ThreadPool createThreadPool(String poolName) { + + int min = GlobalProperties.getInt("threadpool." + poolName + ".min", 0); + int max = GlobalProperties.getInt("threadpool." + poolName + ".max", 100); + + long idle = 1000 * GlobalProperties.getInt("threadpool." + poolName + ".idletime", -1); + if (idle < 0) { + idle = defaultIdleTime; + } + + boolean isDaemon = true; + Integer priority = null; + String threadPriority = GlobalProperties.get("threadpool." + poolName + ".priority", null); + if (threadPriority != null) { + priority = new Integer(threadPriority); + } + + ThreadPool newPool = new ThreadPool(poolName, isDaemon, priority); + newPool.setMaxSize(max); + newPool.setMinSize(min); + newPool.setMaxIdleTime(idle); + + return newPool; + } + +}; diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/thread/Work.java b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/Work.java new file mode 100644 index 000000000..f0b1db9fe --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/Work.java @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.thread; + + +/** + * Used internally by the ThreadPool to wrap a Runnable that is + * going to be run. + * + *

Used to maintains some useful times about the Runnable in terms of when + * it was queued and then eventually run.

+ */ +public class Work { + + /** + * Create a Runnable Work. + */ + public Work(Runnable runnable) { + this.runnable = runnable; + } + + /** + * Return the associated Runnable object. + */ + public Runnable getRunnable() { + return runnable; + } + + /** + * Return the time this work actually started. + */ + public long getStartTime() { + return startTime; + } + + /** + * Sets the time this work actually started. + */ + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + /** + * Return the time this entered the queue. + */ + public long getEnterQueueTime() { + return enterQueueTime; + } + + /** + * Set the time this entered the queue. + */ + public void setEnterQueueTime(long enterQueueTime) { + this.enterQueueTime = enterQueueTime; + } + + /** + * Return the time this left the queue. + */ + public long getExitQueueTime() { + return exitQueueTime; + } + + /** + * Set the time this work left the queue. + */ + public void setExitQueueTime(long exitQueueTime) { + this.exitQueueTime = exitQueueTime; + } + + /** + * The same as getDescription(). + */ + public String toString() { + return getDescription(); + } + + /** + * Return a description of this work. + */ + public String getDescription() { + + StringBuffer sb = new StringBuffer(); + sb.append("Work["); + if (runnable != null){ + sb.append(runnable.toString()); + } + sb.append("]"); + return sb.toString(); + } + + private Runnable runnable; + + private long exitQueueTime; + private long enterQueueTime; + private long startTime; + +}; diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/thread/package.html b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/package.html new file mode 100644 index 000000000..c4e83e0d0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/thread/package.html @@ -0,0 +1,12 @@ + + + + AvajeLib + + +ThreadPool service. +

+Service providing thread pooling for executing background tasks. +

+ + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java new file mode 100644 index 000000000..436a713f5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + + +/** + * A general exception when creating an Object. + */ +public class CreateObjectException extends RuntimeException +{ + static final long serialVersionUID = 7061559938704539736L; + + public CreateObjectException(Exception cause) { + super(cause); + } + + public CreateObjectException(String s, Exception cause) { + super(s, cause); + } + + public CreateObjectException(String s) { + super(s); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java new file mode 100644 index 000000000..9986b11fc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java @@ -0,0 +1,345 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * A lightweight tree structure for simple XML handling. + *

+ * It removes support for nodes being mixed with content. That is, a node can + * only contain content or a list of one or more child nodes. It does not + * support mixing bits of content between the child nodes. + *

+ *

+ * Although designed to simplify XML in supported cases it can be used as a + * general tree structure with attributes of java Objects. + *

+ */ +public class Dnode { + + int level; + + String nodeName; + + String nodeContent; + + ArrayList children; + + LinkedHashMap attrList = new LinkedHashMap(); + + /** + * Create a node. + */ + public Dnode() { + } + + /** + * Parse the raw XML string. + */ + public static Dnode parse(String s){ + DnodeReader r = new DnodeReader(); + return r.parseXml(s); + } + + /** + * Return the node as XML. + */ + public String toXml() { + StringBuilder sb = new StringBuilder(); + generate(sb); + return sb.toString(); + } + + /** + * Generate this node as xml to the buffer. + */ + public StringBuilder generate(StringBuilder sb) { + if (sb == null) { + sb = new StringBuilder(); + } + sb.append("<").append(nodeName); + Iterator it = attributeNames(); + while (it.hasNext()) { + String attr = it.next(); + Object attrValue = getAttribute(attr); + sb.append(" ").append(attr).append("=\""); + if (attrValue != null) { + sb.append(attrValue); + } + sb.append("\""); + } + + if (nodeContent == null && !hasChildren()) { + sb.append(" />"); + + } else { + sb.append(">"); + if (children != null && children.size() > 0) { + for (int i = 0; i < children.size(); i++) { + Dnode child = children.get(i); + child.generate(sb); + } + } + if (nodeContent != null) { + sb.append(nodeContent); + } + sb.append(""); + } + return sb; + } + + /** + * Return the node name. + */ + public String getNodeName() { + return nodeName; + } + + /** + * Set the node name. + */ + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + /** + * Return the node content. + */ + public String getNodeContent() { + return nodeContent; + } + + /** + * Set the node content. + */ + public void setNodeContent(String nodeContent) { + this.nodeContent = nodeContent; + } + + /** + * Return true if this node has children. + */ + public boolean hasChildren() { + return getChildrenCount() > 0; + } + + /** + * Return the number of children this node has. + */ + public int getChildrenCount() { + if (children == null) { + return 0; + } + return children.size(); + } + + /** + * Remove a ancestor node. + */ + public boolean remove(Dnode node) { + if (children == null) { + return false; + } + if (children.remove(node)) { + return true; + } + Iterator it = children.iterator(); + while (it.hasNext()) { + Dnode child = it.next(); + if (child.remove(node)) { + return true; + } + } + return false; + } + + /** + * List of children nodes. + */ + public List children() { + if (children == null) { + return null; + } + return children; + } + + /** + * Add a child. + */ + public void addChild(Dnode child) { + if (children == null) { + children = new ArrayList(); + } + children.add(child); + child.setLevel(level + 1); + } + + /** + * Return the level or depth of the node from the root. + */ + public int getLevel() { + return level; + } + + /** + * Set the level or depth of this node from the root. + */ + public void setLevel(int level) { + this.level = level; + if (children != null) { + for (int i = 0; i < children.size(); i++) { + Dnode child = children.get(i); + child.setLevel(level + 1); + } + } + } + + /** + * Find the first matching node using nodeName. This is a depth first tree + * search. + */ + public Dnode find(String nodeName) { + return find(nodeName, null, null); + } + + /** + * Find the first node matching nodeName and attribute value. This is a + * depth first tree search. + */ + public Dnode find(String nodeName, String attrName, Object value) { + + return find(nodeName, attrName, value, -1); + + } + + /** + * Search for a single node with control over maxLevel. Find the first node + * matching nodeName and attribute value. If attrName and value are null + * then this will just search using the nodeName. This is a depth first tree + * search. Once a matching node is found the search will stop. + */ + public Dnode find(String nodeName, String attrName, Object value, int maxLevel) { + + ArrayList list = new ArrayList(); + findByNode(list, nodeName, true, attrName, value, maxLevel); + if (list.size() >= 1) { + return list.get(0); + } + return null; + } + + /** + * Find all the nodes that match the nodeName. + * + */ + public List findAll(String nodeName, int maxLevel) { + int level = -1; + if (maxLevel > 0) { + level = this.level + maxLevel; + } + return findAll(nodeName, null, null, level); + } + + /** + * Find all the nodes that match the nodeName and attribute value. + */ + public List findAll(String nodeName, String attrName, Object value, int maxLevel) { + + if (nodeName == null && attrName == null) { + throw new RuntimeException("You can not have both nodeName and attrName null"); + } + ArrayList list = new ArrayList(); + findByNode(list, nodeName, false, attrName, value, maxLevel); + return list; + } + + /** + * Used for recursive calling. + */ + private void findByNode(List list, String node, boolean findOne,String attrName, Object value, int maxLevel) { + + if (findOne && list.size() == 1) { + return; + } + if (node == null || node.equals(nodeName)) { + if (attrName == null || value.equals(getAttribute(attrName))) { + list.add(this); + if (findOne) { + return; + } + } + } + if (maxLevel > 0 && level >= maxLevel) { + // hit max level + + } else if (children != null) { + // recursively search the children + for (int i = 0; i < children.size(); i++) { + Dnode child = children.get(i); + child.findByNode(list, node, findOne, attrName, value,maxLevel); + } + } + } + + /** + * The attribute names as strings. + */ + public Iterator attributeNames() { + return attrList.keySet().iterator(); + } + + /** + * Return the attribute for a given name. + */ + public String getAttribute(String name) { + return attrList.get(name); + } + + /** + * Returns an Attribute as a String. + *

+ * Will throw a ClassCastException if the attribute is not a String. + *

+ */ + public String getStringAttr(String name, String defaultValue) { + Object o = attrList.get(name); + if (o == null){ + return defaultValue; + } else { + return o.toString(); + } + } + + /** + * Set an attribute. + */ + public void setAttribute(String name, String value) { + attrList.put(name, value); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[").append(getNodeName()).append(" ").append(attrList).append("]"); + return sb.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java new file mode 100644 index 000000000..57c1b0263 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java @@ -0,0 +1,219 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +/** + * Parse an xml document into a Dnode tree. + */ +public class DnodeParser extends DefaultHandler { + + /** + * The root of the DContent tree. + */ + Dnode root; + + /** + * The current node being parsed. + */ + Dnode currentNode; + + /** + * The nodeContent buffer. + */ + StringBuilder buffer; + + /** + * Used to stack the nodes. + */ + Stack stack = new Stack(); + + /** + * The class used to construct new nodes. Should be Dnode or a subtype of + * Dnode. + */ + Class nodeClass = Dnode.class; + + int depth = 0; + + /** + * Trim whitespace from the content. + */ + boolean trimWhitespace = true; + + /** + * The name of the tag that contains html content + */ + String contentName; + + /** + * The depth of the tag that contains the html content + */ + int contentDepth; + + + /** + * If true then trim the whitespace from the content. + */ + public boolean isTrimWhitespace() { + return trimWhitespace; + } + + /** + * Set whether to trim whitespace from the content. + */ + public void setTrimWhitespace(boolean trimWhitespace) { + this.trimWhitespace = trimWhitespace; + } + + /** + * Return the root node of the DContent tree. + */ + public Dnode getRoot() { + return root; + } + + /** + * Set the type class of node to be created. + */ + public void setNodeClass(Class nodeClass) { + this.nodeClass = nodeClass; + } + + /** + * Create a new Dnode using the nodeClass. + */ + private Dnode createNewNode() { + try { + return (Dnode) nodeClass.newInstance(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + + /** + * process a startElement. + */ + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + super.startElement(uri, localName, qName, attributes); + depth++; + + boolean isContent = (contentName != null); + + if (isContent){ + // must be html content... add the begin tag as content + buffer.append("<").append(localName); + for (int i = 0; i < attributes.getLength(); i++) { + String key = attributes.getLocalName(i); + String val = attributes.getValue(i); + buffer.append(" ").append(key).append("='").append(val).append("'"); + } + buffer.append(">"); + return; + + } + + buffer = new StringBuilder(); + Dnode node = createNewNode(); + node.setNodeName(localName); + for (int i = 0; i < attributes.getLength(); i++) { + String key = attributes.getLocalName(i); + String val = attributes.getValue(i); + node.setAttribute(key, val); + if ("type".equalsIgnoreCase(key) && "content".equalsIgnoreCase(val)) { + // this tag contains html content + // no more nodes until end tag is found + contentName = localName; + contentDepth = depth-1; + } + + } + if (root == null) { + root = node; + } + if (currentNode != null) { + currentNode.addChild(node); + } + stack.push(node); + currentNode = node; + + } + + /** + * append the node content. + */ + public void characters(char[] ch, int start, int length) throws SAXException { + super.characters(ch, start, length); + String s = new String(ch, start, length); + int p = s.indexOf('\r'); + int p2 = s.indexOf('\n'); + if (p == -1 && p2 > -1) { + // This is probably not an issue but tidys up content + // in my text editor + s = StringHelper.replaceString(s, "\n", "\r\n"); + } + buffer.append(s); + } + + /** + * process the endElement. + */ + public void endElement(String uri, String localName, String qName) throws SAXException { + super.endElement(uri, localName, qName); + depth--; + + if (contentName != null){ + // is this the end of the content? + if (contentName.equals(localName) && contentDepth == depth){ + contentName = null; + + } else { + // the html content end tag + buffer.append(""); + } + return; + } + String content = buffer.toString(); + buffer.setLength(0); + if (content.length() > 0) { + if (trimWhitespace) { + content = content.trim(); + } + if (content.length() > 0) { + currentNode.setNodeContent(content); + } + } + stack.pop(); + if (!stack.isEmpty()) { + // get the new currentNode + currentNode = (Dnode) stack.pop(); + stack.push(currentNode); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java new file mode 100644 index 000000000..182d93211 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.StringReader; + +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * Parses an XML inputstream returning a Dnode tree. + */ +public class DnodeReader { + + public Dnode parseXml(String str) { + + try { + ByteArrayOutputStream bao = new ByteArrayOutputStream(str.length()); + OutputStreamWriter osw = new OutputStreamWriter(bao); + + StringReader sr = new StringReader(str); + + int charBufferSize = 1024; + char[] buf = new char[charBufferSize]; + int len; + while ((len = sr.read(buf, 0, buf.length)) != -1) { + osw.write(buf, 0, len); + } + sr.close(); + osw.flush(); + osw.close(); + + bao.flush(); + bao.close(); + + InputStream is = new ByteArrayInputStream(bao.toByteArray()); + return parseXml(is); + + } catch (IOException ex){ + throw new RuntimeException(ex); + } + } + + /** + * Parse the XML inputstream returning the Dnode tree. + */ + public Dnode parseXml(InputStream in) { + + try { + InputSource inSource = new InputSource(in); + + DnodeParser parser = new DnodeParser(); + + XMLReader myReader = XMLReaderFactory.createXMLReader(); + myReader.setContentHandler(parser); + + myReader.parse(inSource); + + return parser.getRoot(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java new file mode 100644 index 000000000..be7b1d07a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +/** + * A general exception that can be used for multiple purposes. + */ +public class GeneralException extends RuntimeException { + + private static final long serialVersionUID = 5783084420007103280L; + + public GeneralException(Exception cause) { + super(cause); + } + + public GeneralException(String s, Exception cause) { + super(s, cause); + } + + public GeneralException(String s) { + super(s); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java new file mode 100644 index 000000000..7046d20db --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + + +/** + * A general exception for invalid data. + */ +public class InvalidDataException extends RuntimeException +{ + static final long serialVersionUID = 7061559938704539846L; + + public InvalidDataException(Exception cause) { + super(cause); + } + + public InvalidDataException(String s, Exception cause) { + super(s, cause); + } + + public InvalidDataException(String s) { + super(s); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java new file mode 100644 index 000000000..e87f4b299 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +/** + * An Email address with an associated alias. + */ +public class MailAddress { + + + String alias; + + String emailAddress; + + /** + * Create an address with an optional alias. + */ + public MailAddress(String alias, String emailAddress){ + this.alias = alias; + this.emailAddress = emailAddress; + } + + /** + * Return the alias. + * If the alias is null this returns an empty string. + */ + public String getAlias() { + if (alias == null){ + return ""; + } + return alias; + } + + /** + * Return the email address. + */ + public String getEmailAddress(){ + return emailAddress; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getAlias()).append(" ").append("<").append(getEmailAddress()).append(">"); + return sb.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java new file mode 100644 index 000000000..444c5a8a4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +/** + * Represents the success or failure of a mail send. + */ +public class MailEvent { + + + /** + * The error indicating a send failure. + */ + Throwable error; + + /** + * The message that was sent. + */ + MailMessage message; + + + /** + * The message send failed with an error. + */ + public MailEvent(MailMessage message, Throwable error){ + this.message = message; + this.error = error; + } + + /** + * The message that we attempted to send. + */ + public MailMessage getMailMessage() { + return message; + } + + /** + * Returns true if the message was sent successfully. + */ + public boolean wasSuccessful() { + return (error == null); + } + + /** + * The error indicating the send failed. + */ + public Throwable getError() { + return error; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java new file mode 100644 index 000000000..0c682aeb8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +/** + * Listens to see if the message was successfully sent. + */ +public interface MailListener { + + /** + * Handle the message event. + */ + public void handleEvent(MailEvent event); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java new file mode 100644 index 000000000..07fccd8ab --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java @@ -0,0 +1,176 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +/** + * A simple test message that can be sent via smtp. + */ +public class MailMessage { + +// /** +// * The subject text. +// */ +// String subject; + + /** + * The body content. + */ + ArrayList bodylines; + + /** + * The sender email address. + */ + MailAddress senderAddress; + + /** + * The headers. + */ + HashMap header = new HashMap(); + + /** + * the recipient of the email. + */ + MailAddress currentRecipient; + + /** + * The list of recipients. + */ + ArrayList recipientList = new ArrayList(); + + /** + * Create the message. + */ + public MailMessage() { + bodylines = new ArrayList(); + } + + /** + * Set the current recipient. + */ + public void setCurrentRecipient(MailAddress currentRecipient){ + this.currentRecipient = currentRecipient; + } + + /** + * Return the current recipient. + */ + public MailAddress getCurrentRecipient() { + return currentRecipient; + } + + /** + * Add a recipient. + */ + public void addRecipient(String alias, String emailAddress){ + recipientList.add(new MailAddress(alias, emailAddress)); + } + + /** + * Set the sender details. + */ + public void setSender(String alias, String senderEmail){ + this.senderAddress = new MailAddress(alias, senderEmail); + } + /** + * Return the sender address. + */ + public MailAddress getSender() { + return senderAddress; + } + + /** + * Return the recipient list. + */ + public Iterator getRecipientList() { + return recipientList.iterator(); + } + + /** + * add a header to the message. + */ + public void addHeader(String key, String val) { + header.put(key, val); + } + + /** + * Set the subject text. + */ + public void setSubject(String subject){ + addHeader("Subject", subject); + } + + /** + * Return the subject text. + */ + public String getSubject() { + return getHeader("Subject"); + } + + /** + * Add text to the body. + */ + public void addBodyLine(String line) { + bodylines.add(line); + } + + /** + * Return the body text. + */ + public Iterator getBodyLines() { + return bodylines.iterator(); + } + + /** + * Return the headers. + */ + public Iterator getHeaderFields() { + return header.keySet().iterator(); + } + + /** + * Return a given header. + */ + public String getHeader(String key) { + return header.get(key); + } + + public String toString() { + StringBuilder sb = new StringBuilder(100); + sb.append("Sender: " + senderAddress + "\tRecipient: " + recipientList + "\n"); + Iterator hi = header.keySet().iterator(); + while (hi.hasNext()) { + String key = hi.next(); + String hline = key + ": " + header.get(key) + "\n"; + sb.append(hline); + } + sb.append("\n"); + Iterator e = bodylines.iterator(); + while (e.hasNext()) { + sb.append(e.next()).append("\n"); + } + return sb.toString(); + } +} + + + diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java new file mode 100644 index 000000000..3225e0d81 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java @@ -0,0 +1,231 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Sends simple MailMessages via smtp. + */ +public class MailSender implements Runnable { + + private static final Logger logger = Logger.getLogger(MailSender.class.getName()); + + int traceLevel = 0; + + Socket sserver; + String server; + + BufferedReader in; + + OutputStreamWriter out; + + MailMessage message; + + MailListener listener = null; + + private static final int SMTP_PORT = 25; + + /** + * Create for a given mail server. + */ + public MailSender(String server) { + this.server = server; + } + + /** + * Set the listener to handle MessageEvents. + */ + public void setMailListener(MailListener listener){ + this.listener = listener; + } + + /** + * Send the message. + */ + public void run() { + send(message); + } + + /** + * Send the message in a background thread. + */ + public void sendInBackground(MailMessage message){ + this.message = message; + Thread thread = new Thread(this); + thread.start(); + } + + + /** + * Send the message in the current thread. + */ + public void send(MailMessage message){ + try { + Iterator i = message.getRecipientList(); + while (i.hasNext()) { + MailAddress recipientAddress = (MailAddress) i.next(); + sserver = new Socket(server, SMTP_PORT); + send(message, sserver, recipientAddress); + sserver.close(); + + if (listener != null){ + MailEvent event = new MailEvent(message, null); + listener.handleEvent(event); + } + } + } catch (Exception ex) { + if (listener != null){ + MailEvent event = new MailEvent(message, ex); + listener.handleEvent(event); + } else { + logger.log(Level.SEVERE, null, ex); + } + } + } + + private void send(MailMessage message, Socket sserver, MailAddress recipientAddress) throws IOException { + + // A bit convoluted, but doesn't depend on DNS in any way... + InetAddress localhost = sserver.getLocalAddress(); + String localaddress = localhost.getHostAddress(); + MailAddress sender = message.getSender(); + message.setCurrentRecipient(recipientAddress); + + // Mandatory header fields, Date and From + if (message.getHeader("Date") == null){ + message.addHeader("Date", new java.util.Date().toString()); + } + if (message.getHeader("From") == null){ + message.addHeader("From", sender.getAlias() + " <" + sender.getEmailAddress() + ">"); + } + + //if (message.getHeader("From") == null){ + message.addHeader("To", recipientAddress.getAlias() + " <" + recipientAddress.getEmailAddress() + ">"); + //} + + out = new OutputStreamWriter(sserver.getOutputStream()); + in = new BufferedReader(new InputStreamReader(sserver.getInputStream())); + String sintro = readln(); + if (!sintro.startsWith("220")) { // 220 + logger.fine("SmtpSender: intro==" + sintro); + return; + } + + writeln("EHLO " + localaddress); + if (!expect250()) { + return; + } + + writeln("MAIL FROM:<" + sender.getEmailAddress() + ">"); + if (!expect250()) { + return; + } + writeln("RCPT TO:<" + recipientAddress.getEmailAddress() + ">"); + if (!expect250()){ + return; + } + writeln("DATA"); + while (true) { // may be multiple 250 replies pending from server + String line = readln(); + if (line.startsWith("3")) + break; // ready to send + if (!line.startsWith("2")) { + logger.fine("SmtpSender.send reponse to DATA: " + line); + return; + } + } + Iterator hi = message.getHeaderFields(); + while (hi.hasNext()) { + String key = (String) hi.next(); + writeln(key + ": " + message.getHeader(key)); + } + writeln(""); // end of header; + Iterator e = message.getBodyLines(); + while (e.hasNext()) { + String bline = (String) e.next(); + if (bline.startsWith(".")) { + bline = "." + bline; + } + writeln(bline); + } + writeln("."); + expect250(); + writeln("QUIT"); + + } + + private boolean expect250() throws IOException { + String line = readln(); + if (!line.startsWith("2")) { + logger.info("SmtpSender.expect250: " + line); + return false; + } + return true; + } + + private void writeln(String s) throws IOException { + if (traceLevel > 2){ + logger.fine("From client: " + s); + } + out.write(s + "\r\n"); + out.flush(); + } + + private String readln() throws IOException { + String line = in.readLine(); + if (traceLevel > 1){ + logger.fine("From server: " + line); + } + return line; + } + + + /** + * Set the trace level. + */ + public void setTraceLevel(int traceLevel){ + this.traceLevel = traceLevel; + } + + /** + * Return the hostname of the local machine. + */ + public String getLocalHostName() { + try { + InetAddress ipaddress = InetAddress.getLocalHost(); + String localHost = ipaddress.getHostName(); + if (localHost == null) { + return "localhost"; + } else { + return localHost; + } + } catch (UnknownHostException e) { + return "localhost"; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java new file mode 100644 index 000000000..1dcf1eeb3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +import java.util.LinkedHashMap; + +/** + * Utility String class that supports String manipulation functions. + */ +public class MapFromString { + + LinkedHashMap map = new LinkedHashMap(); + + String mapToString; + + int stringLength; + int keyStart = 0; + int eqPos = 0; + int valEnd = 0; + + public static LinkedHashMap parse(String mapToString) { + MapFromString c = new MapFromString(mapToString); + return c.parse(); + } + + private MapFromString(String mapToString) { + if (mapToString.charAt(0) == '{'){ + mapToString = mapToString.substring(1); + } + if (mapToString.charAt(mapToString.length()-1) == '}'){ + mapToString = mapToString.substring(0, mapToString.length()-1); + } + + this.mapToString = mapToString; + this.stringLength = mapToString.length(); + } + + private LinkedHashMap parse() { + while(findNext()){ + } + return map; + } + + private boolean findNext() { + if (keyStart > stringLength){ + return false; + } + eqPos = mapToString.indexOf("=",keyStart); + if (eqPos == -1){ + throw new RuntimeException("No = after "+keyStart); + } + valEnd = mapToString.indexOf(", ",eqPos); + if (valEnd == -1){ + valEnd = mapToString.length(); + } + // check that the next valEnd occurs after the next eqPos + + String keyValue = mapToString.substring(keyStart,eqPos); + String valValue = mapToString.substring(eqPos+1,valEnd); + map.put(keyValue, valValue); + keyStart = valEnd + 2; + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java new file mode 100644 index 000000000..1d4eb1bcb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * Helper methods to determine the mime type based on a file name. + */ +public class MimeTypeHelper { + + /** + * Return the mimeType for a given file path. + * This will extract the file extension, and then use that + * to look up an appropriate mime type (from the mimetypes.props file). + * + * To add a new mime type, add it to the mimetype.props file. + */ + public static String getMimeType(String filePath) { + + int lastPeriod = filePath.lastIndexOf("."); + if (lastPeriod > -1) { + filePath = filePath.substring(lastPeriod+1); + } + + try { + return resources.getString(filePath.toLowerCase()); + + } catch (MissingResourceException e) { + return null; + //String m = "Unable to locate mimetype for ["+filePath.toLowerCase()+"] in mimetypes.properties"; + //throw new NotFoundException(m); + } + + } + + private static ResourceBundle resources = ResourceBundle.getBundle("com.avaje.lib.util.mimetypes"); + + +}; diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java new file mode 100644 index 000000000..86ab67623 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + + +/** + * A general exception where data is not found. + */ +public class NotFoundException extends RuntimeException +{ + static final long serialVersionUID = 7061559938704539845L; + + public NotFoundException(Exception cause) { + super(cause); + } + + public NotFoundException(String s, Exception cause) { + super(s, cause); + } + + public NotFoundException(String s) { + super(s); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java new file mode 100644 index 000000000..ed956b31a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java @@ -0,0 +1,616 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility String class that supports String manipulation functions. + */ +public class StringHelper { + + private static final char SINGLE_QUOTE = '\''; + + private static final char DOUBLE_QUOTE = '"'; + + /** + * parses a String of the form name1='value1' name2='value2'. Note that you + * can use either single or double quotes for any particular name value pair + * and the end quote must match the begin quote. + */ + public static HashMap parseNameQuotedValue(String tag) throws RuntimeException { + + if (tag == null || tag.length() < 1) { + return null; + } + + // make sure that the quotes are matched... + // int remainer = countOccurances(tag, ""+quote) % 2; + // if (remainer == 1) { + // dp("remainder = "+remainer); + // throw new StringParsingException("Unmatched quote in "+tag); + // } + + // make sure that th last character is not an equals... + // (check now so I don't need to check this every time..) + if (tag.charAt(tag.length() - 1) == '=') { + throw new RuntimeException("missing quoted value at the end of " + tag); + } + + HashMap map = new HashMap(); + // recursively parse out the name value pairs... + return parseNameQuotedValue(map, tag, 0); + } + + /** + * recursively parse out name value pairs (where the value is quoted, with + * either single or double quotes). + */ + private static HashMap parseNameQuotedValue(HashMap map, + String tag, int pos) throws RuntimeException { + + int equalsPos = tag.indexOf("=", pos); + if (equalsPos > -1) { + // check for begin quote... + char firstQuote = tag.charAt(equalsPos + 1); + if (firstQuote != SINGLE_QUOTE && firstQuote != DOUBLE_QUOTE) { + throw new RuntimeException("missing begin quote at " + (equalsPos) + "[" + + tag.charAt(equalsPos + 1) + "] in [" + tag + "]"); + } + + // check for end quote... + int endQuotePos = tag.indexOf(firstQuote, equalsPos + 2); + if (endQuotePos == -1) { + throw new RuntimeException("missing end quote [" + firstQuote + "] after " + pos + + " in [" + tag + "]"); + } + + // we have a valid name and value... + // dp("pos="+pos+" equalsPos="+equalsPos+" + // endQuotePos="+endQuotePos); + String name = tag.substring(pos, equalsPos); + String value = tag.substring(equalsPos + 2, endQuotePos); + // dp("name="+name+"; value="+value+";"); + + // trim off any whitespace from the front of name... + name = trimFront(name, " "); + if ((name.indexOf(SINGLE_QUOTE) > -1) || (name.indexOf(DOUBLE_QUOTE) > -1)) { + throw new RuntimeException("attribute name contains a quote [" + name + "]"); + } + map.put(name, value); + + return parseNameQuotedValue(map, tag, endQuotePos + 1); + + } else { + // no more equals... stop parsing... + return map; + } + } + + /** + * Returns the number of times a particular String occurs in another String. + * e.g. count the number of single quotes. + */ + public static int countOccurances(String content, String occurs) { + return countOccurances(content, occurs, 0, 0); + } + + private static int countOccurances(String content, String occurs, int pos, int countSoFar) { + int equalsPos = content.indexOf(occurs, pos); + if (equalsPos > -1) { + countSoFar = countSoFar + 1; + pos = equalsPos + occurs.length(); + // dp("countSoFar="+countSoFar+" pos="+pos); + return countOccurances(content, occurs, pos, countSoFar); + } else { + return countSoFar; + } + } + + /** + * Parses out a list of Name Value pairs that are delimited together. Will + * always return a StringMap. If allNameValuePairs is null, or no name + * values can be parsed out an empty StringMap is returned. + * + * @param allNameValuePairs + * the entire string to be parsed. + * @param listDelimiter + * (typically ';') the delimited between the list + * @param nameValueSeparator + * (typically '=') the separator between the name and value + */ + public static Map delimitedToMap(String allNameValuePairs, + String listDelimiter, String nameValueSeparator) { + + HashMap params = new HashMap(); + if ((allNameValuePairs == null) || (allNameValuePairs.length() == 0)) { + return params; + } + // trim off any leading listDelimiter... + allNameValuePairs = trimFront(allNameValuePairs, listDelimiter); + return getKeyValue(params, 0, allNameValuePairs, listDelimiter, nameValueSeparator); + } + + /** + * Trims off recurring strings from the front of a string. + * + * @param source + * the source string + * @param trim + * the string to trim off the front + */ + public static String trimFront(String source, String trim) { + if (source == null) { + return null; + } + if (source.indexOf(trim) == 0) { + // dp("trim ..."); + return trimFront(source.substring(trim.length()), trim); + } else { + return source; + } + } + + /** + * Return true if the value is null or an empty string. + */ + public static boolean isNull(String value) { + if (value == null || value.trim().length() == 0) { + return true; + } + return false; + } + + /** + * Recursively pulls out the key value pairs from a raw string. + */ + private static HashMap getKeyValue(HashMap map, int pos, + String allNameValuePairs, String listDelimiter, String nameValueSeparator) { + + if (pos >= allNameValuePairs.length()) { + // dp("end as "+pos+" >= "+allNameValuePairs.length() ); + return map; + } + + int equalsPos = allNameValuePairs.indexOf(nameValueSeparator, pos); + int delimPos = allNameValuePairs.indexOf(listDelimiter, pos); + + if (delimPos == -1) { + delimPos = allNameValuePairs.length(); + } + if (equalsPos == -1) { + // dp("no more equals..."); + return map; + } + if (delimPos == (equalsPos + 1)) { + // dp("Ignoring as nothing between delim and equals... + // delim:"+delimPos+" eq:"+equalsPos); + return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter, + nameValueSeparator); + } + if (equalsPos > delimPos) { + // there is a key without a value? + String key = allNameValuePairs.substring(pos, delimPos); + key = key.trim(); + if (key.length() > 0) { + map.put(key, null); + } + return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter, + nameValueSeparator); + + } + String key = allNameValuePairs.substring(pos, equalsPos); + + if (delimPos > -1) { + String value = allNameValuePairs.substring(equalsPos + 1, delimPos); + // dp("cont "+key+","+value+" pos:"+pos+" + // len:"+allNameValuePairs.length()); + key = key.trim(); + + map.put(key, value); + pos = delimPos + 1; + + // recurse the rest of the values... + return getKeyValue(map, pos, allNameValuePairs, listDelimiter, nameValueSeparator); + } else { + // dp("ERROR: delimPos < 0 ???"); + return map; + } + } + + /** + * Convert a string that has delimited values (say comma delimited) in a + * String[]. You must explicitly choose whether or not to include empty + * values (say two commas that a right beside each other. + * + *

+ * e.g. "alpha,beta,,theta"
+ * With keepEmpties true, this results in a String[] of size 4 with the + * third one having a String of 0 length. With keepEmpties false, this + * results in a String[] of size 3. + *

+ *

+ *

+ *

+ * e.g. ",alpha,beta,,theta,"
+ * With keepEmpties true, this results in a String[] of size 6 with the + * 1st,4th and 6th one having a String of 0 length. With keepEmpties false, + * this results in a String[] of size 3. + *

+ */ + public static String[] delimitedToArray(String str, String delimiter, boolean keepEmpties) { + + ArrayList list = new ArrayList(); + int startPos = 0; + delimiter(str, delimiter, keepEmpties, startPos, list); + String[] result = new String[list.size()]; + return (String[]) list.toArray(result); + } + + private static void delimiter(String str, String delimiter, boolean keepEmpties, int startPos, + ArrayList list) { + + int endPos = str.indexOf(delimiter, startPos); + if (endPos == -1) { + if (startPos <= str.length()) { + String lastValue = str.substring(startPos, str.length()); + // dp("lastValue="+lastValue); + if (!keepEmpties && lastValue.length() == 0) { + // dp("not keeping..."); + } else { + list.add(lastValue); + } + } + // we have finished parsing the string... + return; + } else { + // get the delimited value... add it.. + String value = str.substring(startPos, endPos); + // dp(startPos+","+endPos+" value="+value); + if (!keepEmpties && value.length() == 0) { + // dp("not keeping..."); + } else { + list.add(value); + } + // recursively search as we are not at the end yet... + delimiter(str, delimiter, keepEmpties, endPos + 1, list); + } + } + + /** + * This returns the FIRST string in str that is bounded on the left by + * leftBound, and bounded on the right by rightBound. This will return null + * if the leftBound is not found within str. + * + *

+ * If leftBound can't be found this returns null. + *

+ *

+ * This rightBound can't be found then this throws a + * StringIndexOutOfBoundsException. + *

+ * + * @param str + * the base string that we will search for the bounded string. + * @param leftBound + * the left bound of the string. + * @param rightBound + * the right bound of the string. + */ + public static String getBoundedString(String str, String leftBound, String rightBound) + throws RuntimeException { + + if (str == null) { + throw new RuntimeException("string to parse is null?"); + } + int startPos = str.indexOf(leftBound); + if (startPos > -1) { + startPos = startPos + leftBound.length(); + int endPos = str.indexOf(rightBound, startPos); + // dp(str+" start:"+startPos+" end:"+endPos); + if (endPos == -1) { + throw new RuntimeException("Can't find rightBound: " + rightBound); + } + return str.substring(startPos, endPos); + } else { + // if no leftBound can be found.. return null... could be in a + // search n parse type loop? + // this keeps "no tag"==null different from "tag not formed + // properly"==StringParsingException + return null; + } + } + + /** + * Takes the String bounded by leftBound & rightBound, and replaces it with + * replaceString. Actually removes the left and right bound strings aswell. + */ + public static String setBoundedString(String str, String leftBound, String rightBound, + String replaceString) { + + int startPos = str.indexOf(leftBound); + if (startPos > -1) { + // startPos = startPos; + int endPos = str.indexOf(rightBound, startPos + leftBound.length()); + if (endPos > -1) { + String toReplace = str.substring(startPos, endPos + 1); + return replaceString(str, toReplace, replaceString); + } else { + return str; + } + } else { + return str; + } + } + + // public static String replaceString(String str, String oldSub, String + // newSub) { + // + // if (str == null) { + // return null; + // } + // StringBuilder newSB = new StringBuilder(str.length()+20); + // int iPos = 0; + // int iPrevPos = 0; + // + // while (true) { + // iPos = str.indexOf(oldSub, iPrevPos); + // if (iPos > -1) { + // // found + // newSB.append(str.substring(iPrevPos, iPos)); + // newSB.append(newSub); + // iPrevPos = iPos + oldSub.length(); + // } else { + // // not found + // newSB.append(str.substring(iPrevPos)); + // break; + // } + // } + // + // return newSB.toString(); + // } + + /** + * This method takes a String and will replace all occurrences of the match + * String with that of the replace String. + * + * @param source + * the source string + * @param match + * the string used to find a match + * @param replace + * the string used to replace match with + * @return the source string after the search and replace + */ + public static String replaceString(String source, String match, String replace) { + if (source == null){ + return null; + } + if (replace == null){ + return source; + } + if (match == null){ + throw new NullPointerException("match is null?"); + } + if (match.equals(replace)){ + return source; + } + return replaceString(source, match, replace, 30, 0, source.length()); + } + + /** + * Additionally specify the additionalSize to add to the buffer. This will + * make the buffer bigger so that it doesn't have to grow when replacement + * occurs. + */ + public static String replaceString(String source, String match, String replace, + int additionalSize, int startPos, int endPos) { + + if (source == null){ + return source; + } + + char match0 = match.charAt(0); + + int matchLength = match.length(); + + if (matchLength == 1 && replace.length() == 1) { + char replace0 = replace.charAt(0); + return source.replace(match0, replace0); + } + if (matchLength >= replace.length()) { + additionalSize = 0; + } + + + int sourceLength = source.length(); + int lastMatch = endPos - matchLength; + + StringBuilder sb = new StringBuilder(sourceLength + additionalSize); + + if (startPos > 0) { + sb.append(source.substring(0, startPos)); + } + + char sourceChar; + boolean isMatch; + int sourceMatchPos; + + for (int i = startPos; i < sourceLength; i++) { + sourceChar = source.charAt(i); + if (i > lastMatch || sourceChar != match0) { + sb.append(sourceChar); + + } else { + // check to see if this is a match + isMatch = true; + sourceMatchPos = i; + + // check each following character... + for (int j = 1; j < matchLength; j++) { + sourceMatchPos++; + if (source.charAt(sourceMatchPos) != match.charAt(j)) { + isMatch = false; + break; + } + } + if (isMatch) { + i = i + matchLength - 1; + sb.append(replace); + } else { + // was not a match + sb.append(sourceChar); + } + } + } + + return sb.toString(); + } + + /** + * A search and replace with multiple matching strings. + *

+ * Useful when converting CRNL CR and NL all to a BR tag for example. + *

+ * + *

+	 * String[] multi = { "\r\n", "\r", "\n" };
+	 * content = StringHelper.replaceStringMulti(content, multi, "<br/>");
+	 * 
+ */ + public static String replaceStringMulti(String source, String[] match, String replace) { + return replaceStringMulti(source, match, replace, 30, 0, source.length()); + } + + /** + * Additionally specify an additional size estimate for the buffer plus + * start and end positions. + *

+ * The start and end positions can limit the search and replace. Otherwise + * these default to startPos = 0 and endPos = source.length(). + *

+ */ + public static String replaceStringMulti(String source, String[] match, String replace, + int additionalSize, int startPos, int endPos) { + + int shortestMatch = match[0].length(); + + char[] match0 = new char[match.length]; + for (int i = 0; i < match0.length; i++) { + match0[i] = match[i].charAt(0); + if (match[i].length() < shortestMatch) { + shortestMatch = match[i].length(); + } + } + + StringBuilder sb = new StringBuilder(source.length() + additionalSize); + + char sourceChar; + + int len = source.length(); + int lastMatch = endPos - shortestMatch; + + if (startPos > 0) { + sb.append(source.substring(0, startPos)); + } + + int matchCount = 0; + + for (int i = startPos; i < len; i++) { + sourceChar = source.charAt(i); + if (i > lastMatch) { + sb.append(sourceChar); + } else { + matchCount = 0; + for (int k = 0; k < match0.length; k++) { + if (matchCount == 0 && sourceChar == match0[k]) { + if (match[k].length() + i <= len) { + + ++matchCount; + int j = 1; + for (; j < match[k].length(); j++) { + if (source.charAt(i + j) != match[k].charAt(j)) { + --matchCount; + break; + } + } + if (matchCount > 0) { + i = i + j - 1; + sb.append(replace); + break; + } + } + } + } + if (matchCount == 0) { + sb.append(sourceChar); + } + } + } + + return sb.toString(); + } + + /** + * This method takes a String as an argument and removes all occurrences of + * the supplied Char. It returns the resulting String. + */ + public static String removeChar(String s, char chr) { + + StringBuilder sb = new StringBuilder(s.length()); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c != chr){ + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * This method takes a String as an argument and removes all occurrences of + * the supplied Chars. It returns the resulting String. + */ + public static String removeChars(String s, char[] chr) { + + StringBuilder sb = new StringBuilder(s.length()); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (!charMatch(c, chr)){ + sb.append(c); + } + } + + return sb.toString(); + } + + private static boolean charMatch(int iChr, char[] chr) { + for (int i = 0; i < chr.length; i++) { + if (iChr == chr[i]) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java new file mode 100644 index 000000000..076ec57bc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + + +/** + * A general string parsing exception. + */ +public class StringParsingException extends RuntimeException { + + static final long serialVersionUID = -3070423471260426402L; + + public StringParsingException(String message) { + super(message); + } +}; + diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java new file mode 100644 index 000000000..6ec2abffd --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java @@ -0,0 +1,108 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.lib.util; + +/** + * Builds a string from a stack trace. + *

+ * Generally used to flatten a stack trace into a single string + * removing \r\n and limiting the size of any given stack. + *

+ */ +public class ThrowablePrinter { + + private static final String atString = " at "; + + private String newLineChar = "\\r\\n"; + + private int maxStackTraceLines = 3; + + /** + * Set the maximum number of lines in any one part of + * the stack trace. This is not the total maximum. + */ + public void setMaxStackTraceLines(int maxStackTraceLines) { + this.maxStackTraceLines = maxStackTraceLines; + } + + /** + * Set the new line character used to replace \r\n with. + * This is useful so that the stack is placed on a single line + * in a log file. + */ + public void setNewLineChar(String newLineChar) { + this.newLineChar = newLineChar; + } + + /** + * Convert the error into a string representation. + *

+ * Replaces the \r\n and limits the stack lines. + *

+ */ + public String print(Throwable e) { + StringBuffer sb = new StringBuffer(); + printThrowable(sb, e, false); + + String line = sb.toString(); + line = StringHelper.replaceString(line, "\r", "\\r"); + line = StringHelper.replaceString(line, "\n", "\\n"); + + return line; + } + + /** + * Recursively output the Throwable stack trace to the log. + * + * @param sb the buffer to write the stack trace to + * @param e the source throwable + * @param isCause flag to indicate if this is the top level throwable or a + * cause + */ + protected void printThrowable(StringBuffer sb, Throwable e, boolean isCause) { + if (e != null) { + if (isCause) { + sb.append("Caused by: "); + } + sb.append(e.getClass().getName()); + sb.append(":"); + sb.append(e.getMessage()).append(newLineChar); + + StackTraceElement[] ste = e.getStackTrace(); + int outputStackLines = ste.length; + int notShownCount = 0; + if (ste.length > maxStackTraceLines) { + outputStackLines = maxStackTraceLines; + notShownCount = ste.length - outputStackLines; + } + for (int i = 0; i < outputStackLines; i++) { + sb.append(atString); + sb.append(ste[i].toString()).append(newLineChar); + } + if (notShownCount > 0) { + sb.append(" ... "); + sb.append(notShownCount); + sb.append(" more").append(newLineChar); + } + Throwable cause = e.getCause(); + if (cause != null) { + printThrowable(sb, cause, true); + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/mimetypes.properties b/src/main/java/com/avaje/ebeaninternal/server/lib/util/mimetypes.properties new file mode 100644 index 000000000..8b77c2af1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/mimetypes.properties @@ -0,0 +1,186 @@ + # + # Used by MimeTypeHelper to find the mime types of based on file extensions. + +abs=audio/x-mpeg +ai=application/postscript +aif=audio/x-aiff +aifc=audio/x-aiff +aiff=audio/x-aiff +aim=application/x-aim +art=image/x-jg +asc=text/plain +asf=video/x-ms-asf +asx=video/x-ms-asf +au=audio/basic +avi=video/x-msvideo +avx=video/x-rad-screenplay + +bcpio=application/x-bcpio +bin=application/octet-stream +bmp=image/bmp +body=text/html + +cdf=application/x-netcdf +cer=application/x-x509-ca-cert +class=application/java +cpio=application/x-cpio +csh=application/x-csh +css=text/css +csv=text/csv + +dib=image/bmp +doc=application/msword +dtd=application/xml-dtd +dv=video/x-dv +dvi=application/x-dvi +dms=application/octet-stream + +eps=application/postscript +etx=text/x-setext +exe=application/octet-stream + +gif=image/gif +gtar=application/x-gtar +gz=application/x-gzip + +hdf=application/x-hdf +htc=text/x-component +htm=text/html +html=text/html +hqx=application/mac-binhex40 + +ico=image/x-icon +ief=image/ief + +jad=text/vnd.sun.j2me.app-descriptor +jar=application/java-archive +java=text/plain +jnlp=application/x-java-jnlp-file +jpe=image/jpeg +jpeg=image/jpeg +jpg=image/jpeg +js=text/javascript +jsf=text/plain +jspf=text/plain + +kar=audio/midi + +latex=application/x-latex +lha=application/octet-stream +lzh=application/octet-stream + +m3u=audio/x-mpegurl +mac=image/x-macpaint +man=application/x-troff-man +mathml=application/mathml+xml +me=application/x-troff-me +mid=audio/midi +midi=audio/midi +mif=application/vnd.mif +mov=video/quicktime +movie=video/x-sgi-movie +mp1=audio/x-mpeg +mp2=audio/mpeg +mp3=audio/mpeg +mpa=audio/x-mpeg +mpe=video/mpeg +mpeg=video/mpeg +mpega=audio/x-mpeg +mpga=audio/mpeg +mpg=video/mpeg +mpv2=video/mpeg2 +ms=application/x-troff-ms + +nc=application/x-netcdf + +oda=application/oda +ogg=application/ogg + +pbm=image/x-portable-bitmap +pct=image/pict +pdf=application/pdf +pgm=image/x-portable-graymap +pic=image/pict +pict=image/pict +pls=audio/x-scpls +png=image/png +pnm=image/x-portable-anymap +pnt=image/x-macpaint +ppm=image/x-portable-pixmap +pps=application/vnd.ms-powerpoint +ppt=application/vnd.ms-powerpoint +ps=application/postscript +psd=image/x-photoshop + +qt=video/quicktime +qti=image/x-quicktime +qtif=image/x-quicktime + +ra=audio/x-realaudio +ram=audio/x-pn-realaudio +ras=image/x-cmu-raster +rdf=application/rdf+xml +rgb=image/x-rgb +rpm=audio/x-pn-realaudio-plugin +rm=application/vnd.rn-realmedia +roff=application/x-troff +rtf=text/rtf +rtx=text/richtext + +sh=application/x-sh +shar=application/x-shar +shtml=text/x-server-parsed-html +sgml=text/sgml +sgm=text/sgml +smf=audio/x-midi +sit=application/x-stuffit +snd=audio/basic +src=application/x-wais-source +sv4cpio=application/x-sv4cpio +sv4crc=application/x-sv4crc +svg=image/svg+xml +svgz=image/svg +swf=application/x-shockwave-flash + +t=application/x-troff +tar=application/x-tar +tcl=application/x-tcl +tex=application/x-tex +texi=application/x-texinfo +texinfo=application/x-texinfo +tif=image/tiff +tiff=image/tiff +tr=application/x-troff +tsv=text/tab-separated-values +txt=text/plain + +ulw=audio/basic +ustar=application/x-ustar + +vcd=application/x-cdlink +vrml=model/vrml +vsd=application/x-visio +vxml=application/voicexml+xml + +wav=audio/x-wav +wbmp=image/vnd.wap.wbmp +wml=text/vnd.wap.wml +wmlc=application/vnd.wap.wmlc +wmls=text/vnd.wap.wmlscript +wmlscriptc=application/vnd.wap.wmlscriptc +wrl=model/vrml + +xbm=image/x-xbitmap +xht=application/xhtml+xml +xhtml=application/xhtml+xml +xls=application/vnd.ms-excel +xml=application/xml +xpm=image/x-xpixmap +xsl=application/xml +xslt=application/xslt+xml +xul=application/vnd.mozilla.xul+xml +xwd=image/x-xwindowdump + +Z=application/x-compress +z=application/x-compress +zip=application/zip \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/package.html b/src/main/java/com/avaje/ebeaninternal/server/lib/util/package.html new file mode 100644 index 000000000..5079d4e5b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/package.html @@ -0,0 +1,12 @@ + + + + AvajeLib + + +General utility objects. + +

Provides general utility objects. +

+ + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java new file mode 100644 index 000000000..f873682ea --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -0,0 +1,274 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.loadcontext; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.bean.BeanLoader; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.LoadBeanContext; +import com.avaje.ebeaninternal.api.LoadBeanRequest; +import com.avaje.ebeaninternal.api.LoadContext; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +/** + * Default implementation of LoadBeanContext. + * + */ +public class DLoadBeanContext implements LoadBeanContext, BeanLoader { + + private static final Logger logger = Logger.getLogger(DLoadBeanContext.class.getName()); + + protected final DLoadContext parent; + + protected final BeanDescriptor desc; + + protected final String path; + + protected final String fullPath; + + private final DLoadList weakList; + + private final OrmQueryProperties queryProps; + + private int batchSize; + + public DLoadBeanContext(DLoadContext parent, BeanDescriptor desc, String path, int batchSize, + OrmQueryProperties queryProps, DLoadList weakList) { + + this.parent = parent; + this.desc = desc; + this.path = path; + this.batchSize = batchSize; + this.queryProps = queryProps; + this.weakList = weakList; + + if (parent.getRelativePath() == null) { + this.fullPath = path; + } else { + this.fullPath = parent.getRelativePath() + "." + path; + } + } + + public void configureQuery(SpiQuery query, String lazyLoadProperty) { + + // propagate the readOnly state + if (parent.isReadOnly() != null) { + query.setReadOnly(parent.isReadOnly()); + } + query.setParentNode(getObjectGraphNode()); + query.setLazyLoadProperty(lazyLoadProperty); + + if (queryProps != null) { + queryProps.configureBeanQuery(query); + } + if (parent.isUseAutofetchManager()) { + query.setAutofetch(true); + } + } + + public String getFullPath() { + return fullPath; + } + + public PersistenceContext getPersistenceContext() { + return parent.getPersistenceContext(); + } + + public OrmQueryProperties getQueryProps() { + return queryProps; + } + + public ObjectGraphNode getObjectGraphNode() { + return parent.getObjectGraphNode(path); + } + + public String getPath() { + return path; + } + + public String getName() { + return parent.getEbeanServer().getName(); + } + + public int getBatchSize() { + return batchSize; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + public BeanDescriptor getBeanDescriptor() { + return desc; + } + + public LoadContext getGraphContext() { + return parent; + } + + public void register(EntityBeanIntercept ebi){ + int pos = weakList.add(ebi); + ebi.setBeanLoader(pos, this, parent.getPersistenceContext()); + } + + /** + * Check if we can load the bean from L2 cache. If so avoid loading from the DB. + */ + private boolean loadBeanFromCache(EntityBeanIntercept ebi, int position) { + + if (!desc.loadFromCache(ebi)) { + return false; + } + // we loaded the bean from cache + weakList.removeEntry(position); + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Loading path:" + fullPath + " - bean loaded from L2 cache, position[" + position + "]"); + } + return true; + } + + /** + * Load this bean and potentially a batch of similar beans. + */ + public void loadBean(EntityBeanIntercept ebi) { + + // A synchronized (this) is effectively held by EntityBeanIntercept.loadBean() + + if (desc.lazyLoadMany(ebi)) { + // lazy load property was a Many + return; + } + + int position = ebi.getBeanLoaderIndex(); + boolean hitCache = !parent.isExcludeBeanCache() && desc.isBeanCaching(); + + if (hitCache && loadBeanFromCache(ebi, position)) { + // successfully hit the L2 cache so don't invoke DB lazy loading + return; + } + + // Get a batch of beans to lazy load + List batch = null; + try { + batch = weakList.getLoadBatch(position, batchSize); + } catch (IllegalStateException e) { + logger.log(Level.SEVERE, "type["+desc.getFullName()+"] fullPath[" + fullPath + "] batchSize["+batchSize+"]", e); + } + + if (hitCache && batchSize > 1) { + // Check each of the beans in the batch to see if they are in the L2 cache. + // Add more as necessary to make up our batch that will be loaded. + batch = loadBeanCheckBatch(batch); + } + + if (logger.isLoggable(Level.FINER)) { + for (int i = 0; i < batch.size(); i++) { + + EntityBeanIntercept entityBeanIntercept = batch.get(i); + EntityBean owner = entityBeanIntercept.getOwner(); + Object id = desc.getId(owner); + + logger.finer("LoadBean type["+owner.getClass().getName()+"] fullPath["+fullPath+"] id["+id+"] batchIndex["+i+"] beanLoaderIndex["+entityBeanIntercept.getBeanLoaderIndex()+"]"); + } + } + + LoadBeanRequest req = new LoadBeanRequest(this, batch, null, batchSize, true, ebi.getLazyLoadProperty(), hitCache); + parent.getEbeanServer().loadBean(req); + + } + + /** + * Check each of the beans in the batch to see if they are in the cache. + * Get more beans out as necessary to get our desired batch size. + */ + private List loadBeanCheckBatch(List batch) { + + + List actualLoadBatch = new ArrayList(batchSize); + List batchToCheck = batch; + + int loadedFromCache = 0; + + while (true) { + // check each bean (not already checked) to see if it is in the cache + for (int i = 0; i < batchToCheck.size(); i++) { + if (!desc.loadFromCache(batchToCheck.get(i))) { + actualLoadBatch.add(batchToCheck.get(i)); + } else { + loadedFromCache++; + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Loading path:" + fullPath + " - bean loaded from L2 cache(batch)"); + } + } + } + + if (batchToCheck.isEmpty()) { + // we have exhausted all the beans that need lazy loading + break; + } + int more = batchSize - actualLoadBatch.size(); + if (more <= 0 || loadedFromCache > 500) { + break; + } + // get some more to check as we loaded some from L2 cache + batchToCheck = weakList.getNextBatch(more); + } + return actualLoadBatch; + } + + public void loadSecondaryQuery(OrmQueryRequest parentRequest, int requestedBatchSize, boolean all) { + + synchronized (this) { + do { + List batch = weakList.getNextBatch(requestedBatchSize); + if (batch.size() == 0) { + // there are no beans to load + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Loading path:" + fullPath + " - no more beans to load"); + } + return; + } + boolean loadCache = false; + LoadBeanRequest req = new LoadBeanRequest(this, batch, parentRequest.getTransaction(), requestedBatchSize, false, null, loadCache); + + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Loading path:" + fullPath + " - secondary query batch load [" + batch.size() + "] beans"); + } + + parent.getEbeanServer().loadBean(req); + if (!all) { + break; + } + + } while (true); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java new file mode 100644 index 000000000..57758aa44 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -0,0 +1,337 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.loadcontext; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.LoadContext; +import com.avaje.ebeaninternal.api.LoadSecondaryQuery; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +/** + * Default implementation of LoadContext. + * + * @author rbygrave + */ +public class DLoadContext implements LoadContext { + + private final SpiEbeanServer ebeanServer; + + private final BeanDescriptor rootDescriptor; + + private final Map beanMap = new HashMap(); + private final Map manyMap = new HashMap(); + + private final DLoadBeanContext rootBeanContext; + + private final Boolean readOnly; + private final boolean excludeBeanCache; + private final int defaultBatchSize; + + /** + * The path relative to the root of the object graph. + */ + private final String relativePath; + private final ObjectGraphOrigin origin; + private final boolean useAutofetchManager; + private final boolean hardRefs; + + private final Map nodePathMap = new HashMap(); + + private PersistenceContext persistenceContext; + private List secQuery; + + public DLoadContext(SpiEbeanServer ebeanServer, BeanDescriptor rootDescriptor, Boolean readOnly, SpiQuery query) { + this(ebeanServer, rootDescriptor, readOnly, + Boolean.FALSE.equals(query.isUseBeanCache()), + query.getParentNode(), + query.getAutoFetchManager() != null); + } + + public DLoadContext(SpiEbeanServer ebeanServer, BeanDescriptor rootDescriptor, Boolean readOnly, + boolean excludeBeanCache, ObjectGraphNode parentNode, boolean useAutofetchManager) { + + this.ebeanServer = ebeanServer; + this.hardRefs = GlobalProperties.getBoolean("ebean.hardrefs", false); + this.defaultBatchSize = ebeanServer.getLazyLoadBatchSize(); + this.rootDescriptor = rootDescriptor; + this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null, createBeanLoadList()); + this.readOnly = readOnly; + this.excludeBeanCache = excludeBeanCache; + this.useAutofetchManager = useAutofetchManager; + + if (parentNode != null){ + this.origin = parentNode.getOriginQueryPoint(); + this.relativePath = parentNode.getPath(); + } else { + this.origin = null; + this.relativePath = null; + } + } + + protected boolean isExcludeBeanCache() { + return excludeBeanCache; + } + + /** + * Return the minimum batch size when using QueryIterator with query joins. + */ + public int getSecondaryQueriesMinBatchSize(OrmQueryRequest parentRequest, int defaultQueryBatch) { + + if (secQuery == null){ + return -1; + } + + int maxBatch = 0; + for (int i = 0; i < secQuery.size(); i++) { + int batchSize = secQuery.get(i).getQueryFetchBatch(); + if (batchSize == 0){ + batchSize = defaultQueryBatch; + } + maxBatch = Math.max(maxBatch, batchSize); + } + return maxBatch; + } + + /** + * Execute all the secondary queries. + */ + public void executeSecondaryQueries(OrmQueryRequest parentRequest, int defaultQueryBatch) { + + if (secQuery != null){ + for (int i = 0; i < secQuery.size(); i++) { + OrmQueryProperties properties = secQuery.get(i); + + int batchSize = properties.getQueryFetchBatch(); + if (batchSize == 0){ + batchSize = defaultQueryBatch; + } + LoadSecondaryQuery load = getLoadSecondaryQuery(properties.getPath()); + load.loadSecondaryQuery(parentRequest, batchSize, properties.isQueryFetchAll()); + } + } + } + + /** + * Return the LoadBeanContext or LoadManyContext for the given path. + */ + private LoadSecondaryQuery getLoadSecondaryQuery(String path){ + LoadSecondaryQuery beanLoad = beanMap.get(path); + if (beanLoad == null){ + beanLoad = manyMap.get(path); + } + return beanLoad; + } + + /** + * Remove the +query and +lazy secondary queries and + * register them with their appropriate LoadBeanContext + * or LoadManyContext. + *

+ * The parts of the secondary queries are removed and used + * by LoadBeanContext/LoadManyContext to build the appropriate + * queries. + *

+ */ + public void registerSecondaryQueries(SpiQuery query) { + + secQuery = query.removeQueryJoins(); + if (secQuery != null){ + for (int i = 0; i < secQuery.size(); i++) { + OrmQueryProperties props = secQuery.get(i); + registerSecondaryQuery(props); + } + } + + List lazyQueries = query.removeLazyJoins(); + if (lazyQueries != null){ + for (int i = 0; i < lazyQueries.size(); i++) { + OrmQueryProperties lazyProps = lazyQueries.get(i); + registerSecondaryQuery(lazyProps); + } + } + } + + /** + * Setup the load context at this path with OrmQueryProperties which is + * used to build the appropriate query for +query or +lazy loading. + */ + private void registerSecondaryQuery(OrmQueryProperties props) { + + String propName = props.getPath(); + ElPropertyValue elGetValue = rootDescriptor.getElGetValue(propName); + + boolean many = elGetValue.getBeanProperty().containsMany(); + registerSecondaryNode(many, props); + } + + + public ObjectGraphNode getObjectGraphNode(String path) { + + ObjectGraphNode node = nodePathMap.get(path); + if (node == null){ + node = createObjectGraphNode(path); + nodePathMap.put(path, node); + } + + return node; + } + + private ObjectGraphNode createObjectGraphNode(String path) { + + if (relativePath != null){ + if (path == null){ + path = relativePath; + } else { + path = relativePath+"."+path; + } + } + return new ObjectGraphNode(origin, path); + } + + public boolean isUseAutofetchManager() { + return useAutofetchManager; + } + + public String getRelativePath() { + return relativePath; + } + + protected SpiEbeanServer getEbeanServer() { + return ebeanServer; + } + + /** + * Return the parent state which defines the sharedInstance and readOnly status + * which needs to be propagated to other beans and collections. + */ + protected Boolean isReadOnly() { + return readOnly; + } + + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + public void setPersistenceContext(PersistenceContext persistenceContext) { + this.persistenceContext = persistenceContext; + } + + public void register(String path, EntityBeanIntercept ebi){ + getBeanContext(path).register(ebi); + } + + public void register(String path, BeanCollection bc){ + getManyContext(path).register(bc); + } + + private DLoadBeanContext getBeanContext(String path) { + if (path == null){ + return rootBeanContext; + } + DLoadBeanContext beanContext = beanMap.get(path); + if (beanContext == null){ + beanContext = createBeanContext(path, defaultBatchSize, null); + beanMap.put(path, beanContext); + } + return beanContext; + } + + private void registerSecondaryNode(boolean many, OrmQueryProperties props) { + + String path = props.getPath(); + int lazyJoinBatch = props.getLazyFetchBatch(); + int batchSize = lazyJoinBatch > 0 ? lazyJoinBatch : defaultBatchSize; + + if (many){ + DLoadManyContext manyContext = createManyContext(path, batchSize, props); + manyMap.put(path, manyContext); + } else { + DLoadBeanContext beanContext = createBeanContext(path, batchSize, props); + beanMap.put(path, beanContext); + } + } + + private DLoadManyContext getManyContext(String path) { + if (path == null){ + throw new RuntimeException("path is null?"); + } + DLoadManyContext ctx = manyMap.get(path); + if (ctx == null){ + ctx = createManyContext(path, defaultBatchSize, null); + manyMap.put(path, ctx); + } + return ctx; + } + + private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) { + + BeanPropertyAssocMany p = (BeanPropertyAssocMany)getBeanProperty(rootDescriptor, path); + + return new DLoadManyContext(this, p, path, batchSize, queryProps, createBeanCollectionLoadList()); + } + + + private DLoadList createBeanLoadList() { + if (hardRefs){ + return new DLoadHardList(); + } else { + return new DLoadWeakList(); + } + } + + private DLoadList> createBeanCollectionLoadList() { + if (hardRefs){ + return new DLoadHardList>(); + } else { + return new DLoadWeakList>(); + } + } + + private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) { + + BeanPropertyAssoc p = (BeanPropertyAssoc)getBeanProperty(rootDescriptor, path); + BeanDescriptor targetDescriptor = p.getTargetDescriptor(); + + return new DLoadBeanContext(this, targetDescriptor, path, batchSize, queryProps, createBeanLoadList()); + } + + private BeanProperty getBeanProperty(BeanDescriptor desc, String path){ + + return desc.getBeanPropertyFromPath(path); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadHardList.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadHardList.java new file mode 100644 index 000000000..f4ce75e64 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadHardList.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.loadcontext; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DLoadHardList implements DLoadList { + + private static final Logger logger = Logger.getLogger(DLoadHardList.class.getName()); + + protected final ArrayList list = new ArrayList(); + + protected int removedFromTop; + + protected DLoadHardList() { + + } + + public int add(T e) { + synchronized (this) { + int i = list.size(); + list.add(e); + return i; + } + } + + public void removeEntry(int position) { + synchronized (this) { + T object = list.get(position); + if (object == null) { + logger.log(Level.WARNING, "removeEntry found no Object for position[" + position + "]"); + } else { + // just set the entry to null + list.set(position, null); + } + if (position == removedFromTop) { + removedFromTop++; + } + } + } + + public List getNextBatch(int batchSize) { + if (removedFromTop >= list.size()){ + return new ArrayList(0); + } + return getLoadBatch(removedFromTop, batchSize, true); + } + + public List getLoadBatch(int position, int batchSize) { + return getLoadBatch(position, batchSize, false); + } + + private List getLoadBatch(int position, int batchSize, boolean ignoreMissing) { + + synchronized (this) { + if (batchSize < 1) { + throw new RuntimeException("batchSize " + batchSize + " < 1 ??!!"); + } + + ArrayList batch = new ArrayList(); + + if (!addObjectToBatchAt(batch, position) && !ignoreMissing) { + String msg = "getLoadBatch position[" + position + "] didn't find a bean in the list?"; + throw new IllegalStateException(msg); + } + + for (int i = position; i < list.size(); i++) { + addObjectToBatchAt(batch, i); + if (batch.size() == batchSize) { + // found enough beans going forward + return batch; + } + } + + // search the front of the list to fill our batch + for (int i = removedFromTop; i < position; i++) { + addObjectToBatchAt(batch, i); + if (batch.size() == batchSize) { + // found enough beans going forward from start of list + return batch; + } + } + + return batch; + } + } + + private boolean addObjectToBatchAt(ArrayList batch, int i) { + + boolean found = false; + T object = list.get(i); + if (object != null) { + found = true; + batch.add(object); + // set it to null saying we have loaded this one + list.set(i, null); + } + + if (i == removedFromTop) { + removedFromTop++; + } + return found; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadList.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadList.java new file mode 100644 index 000000000..077db14ca --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadList.java @@ -0,0 +1,21 @@ +package com.avaje.ebeaninternal.server.loadcontext; + +import java.util.List; + +public interface DLoadList { + + public int add(T e); + + /** + * Return the next batch of entries from the top. + */ + public abstract List getNextBatch(int batchSize); + + public void removeEntry(int position); + + /** + * Return the batch of entries based on the position and batch size. + */ + public List getLoadBatch(int position, int batchSize); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java new file mode 100644 index 000000000..cf51b35c0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -0,0 +1,180 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.loadcontext; + +import java.util.List; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.LoadManyContext; +import com.avaje.ebeaninternal.api.LoadManyRequest; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +public class DLoadManyContext implements LoadManyContext, BeanCollectionLoader { + + protected final DLoadContext parent; + + protected final String fullPath; + + private final BeanDescriptor desc; + + private final BeanPropertyAssocMany property; + + private final String path; + + private final int batchSize; + + private final OrmQueryProperties queryProps; + + private final DLoadList> weakList; + + public DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany p, + String path, int batchSize, OrmQueryProperties queryProps, DLoadList> weakList) { + + this.parent = parent; + this.property = p; + this.desc = p.getBeanDescriptor(); + this.path = path; + this.batchSize = batchSize; + this.queryProps = queryProps; + this.weakList = weakList;//new DLoadWeakList>(); + + if (parent.getRelativePath() == null){ + this.fullPath = path; + } else { + this.fullPath = parent.getRelativePath()+"."+path; + } + + } + + public void configureQuery(SpiQuery query){ + + // propagate the readOnly state + if (parent.isReadOnly() != null){ + query.setReadOnly(parent.isReadOnly()); + } + query.setParentNode(getObjectGraphNode()); + + if (queryProps != null){ + queryProps.configureManyQuery(query); + } + + if (parent.isUseAutofetchManager()){ + query.setAutofetch(true); + } + } + + public ObjectGraphNode getObjectGraphNode() { + + // we return the parent node ... as we actually + // query on the parent selecting just it's id + + int pos = path.lastIndexOf('.'); + if (pos == -1){ + return parent.getObjectGraphNode(null); + } else { + String parentPath = path.substring(0, pos); + return parent.getObjectGraphNode(parentPath); + } + } + + public String getFullPath() { + return fullPath; + } + + public PersistenceContext getPersistenceContext() { + return parent.getPersistenceContext(); + } + + public int getBatchSize() { + return batchSize; + } + + public BeanPropertyAssocMany getBeanProperty() { + return property; + } + + public BeanDescriptor getBeanDescriptor() { + return desc; + } + + public String getPath() { + return path; + } + + public String getName() { + return parent.getEbeanServer().getName(); + } + + public void register(BeanCollection bc){ + int pos = weakList.add(bc); + bc.setLoader(pos, this); + } + + public void loadMany(BeanCollection bc, boolean onlyIds) { + + int position = bc.getLoaderIndex(); + + LoadManyRequest req; + synchronized (weakList) { + boolean hitCache = desc.isBeanCaching() && !onlyIds && !parent.isExcludeBeanCache(); + if (hitCache){ + Object ownerBean = bc.getOwnerBean(); + BeanDescriptor parentDesc = desc.getBeanDescriptor(ownerBean.getClass()); + Object parentId = parentDesc.getId(ownerBean); + if (parentDesc.cacheLoadMany(property, bc, parentId, parent.isReadOnly(), false)) { + // we loaded the bean from cache + weakList.removeEntry(position); + return; + } + } + + List> loadBatch = weakList.getLoadBatch(position, batchSize); + req = new LoadManyRequest(this, loadBatch, null, batchSize, true, onlyIds, hitCache); + } + parent.getEbeanServer().loadMany(req); + } + + public void loadSecondaryQuery(OrmQueryRequest parentRequest, int requestedBatchSize, boolean all){ + + do { + LoadManyRequest req; + synchronized (weakList) { + List> batch = weakList.getNextBatch(requestedBatchSize); + if (batch.size() == 0){ + return; + } + req = new LoadManyRequest(this, batch, parentRequest.getTransaction(), requestedBatchSize, false, false, false); + } + parent.getEbeanServer().loadMany(req); + if (!all){ + // queryFirst(batch) + break; + } + } while (true); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadWeakList.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadWeakList.java new file mode 100644 index 000000000..ff4b9972d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadWeakList.java @@ -0,0 +1,134 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.loadcontext; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DLoadWeakList implements DLoadList { + + private static final Logger logger = Logger.getLogger(DLoadWeakList.class.getName()); + + protected final ArrayList> list = new ArrayList>(); + + protected int removedFromTop; + + protected DLoadWeakList() { + + } + + public int add(T e) { + synchronized (this) { + int i = list.size(); + list.add(new WeakReference(e)); + return i; + } + } + + public void removeEntry(int position) { + synchronized (this) { + WeakReference wref = list.get(position); + if (wref == null) { + logger.log(Level.WARNING, "removeEntry found no WeakReference for position[" + position + "]"); + } else { + // just set the entry to null + list.set(position, null); + T object = wref.get(); + if (object == null) { + logger.log(Level.WARNING, "removeEntry found no Object held by WeakReference for position[" + position + "]"); + } + } + if (position == removedFromTop) { + removedFromTop++; + } + } + } + + public List getNextBatch(int batchSize) { + if (removedFromTop >= list.size()){ + return new ArrayList(0); + } + return getLoadBatch(removedFromTop, batchSize, true); + } + + public List getLoadBatch(int position, int batchSize) { + return getLoadBatch(position, batchSize, false); + } + + private List getLoadBatch(int position, int batchSize, boolean ignoreMissing) { + + synchronized (this) { + if (batchSize < 1) { + throw new RuntimeException("batchSize " + batchSize + " < 1 ??!!"); + } + + ArrayList batch = new ArrayList(); + + if (!addObjectToBatchAt(batch, position) && !ignoreMissing) { + String msg = "getLoadBatch position[" + position + "] didn't find a bean in the list?"; + throw new IllegalStateException(msg); + } + + for (int i = position; i < list.size(); i++) { + addObjectToBatchAt(batch, i); + if (batch.size() == batchSize) { + // found enough beans going forward + return batch; + } + } + + // search the front of the list to fill our batch + for (int i = removedFromTop; i < position; i++) { + addObjectToBatchAt(batch, i); + if (batch.size() == batchSize) { + // found enough beans going forward from start of list + return batch; + } + } + + return batch; + } + } + + private boolean addObjectToBatchAt(ArrayList batch, int i) { + + boolean found = false; + WeakReference wref = list.get(i); + if (wref != null) { + T object = wref.get(); + if (object == null) { + logger.log(Level.WARNING, "Bean is null from weak reference"); + } else { + found = true; + batch.add(object); + } + // set it to null saying we have loaded this one + list.set(i, null); + } + if (i == removedFromTop) { + removedFromTop++; + } + return found; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/package-info.java new file mode 100644 index 000000000..90cdc170a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/package-info.java @@ -0,0 +1,4 @@ +/** + * Load context (supports batch lazy loading). + */ +package com.avaje.ebeaninternal.server.loadcontext; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java new file mode 100644 index 000000000..39047127d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java @@ -0,0 +1,269 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; + +/** + * Controls the batch ordering of persist requests. + *

+ * Persist requests include bean inserts updates deletes and UpdateSql and + * CallableSql requests. + *

+ *

+ * This object queues up the requests into appropriate entries according to the + * 'depth' and the 'type' of the requests. The depth relates to how saves and + * deletes cascade following the associations of a bean. For saving Associated + * One cascades reduce the depth (-1) and associated many's increase the depth. + * The initial depth of a request is 0. + *

+ */ +public final class BatchControl { + + private static final Logger logger = Logger.getLogger(BatchControl.class.getName()); + + /** + * Used to sort queue entries by depth. + */ + private static final BatchDepthComparator depthComparator = new BatchDepthComparator(); + + /** + * The associated transaction. + */ + private final SpiTransaction transaction; + + /** + * Controls batching of the PreparedStatements. This should be flushed after + * each 'depth'. + */ + private final BatchedPstmtHolder pstmtHolder = new BatchedPstmtHolder(); + + /** + * The size at which the batch queue will flush. This should be close to the + * number of statements that are batched into a single PreparedStatement. This + * size relates to the size of a list in a BatchQueueEntry and not the total + * number of request which could be more than that. + */ + private int batchSize; + + /** + * If true try to get generated keys from inserts. + */ + private boolean getGeneratedKeys; + + private boolean batchFlushOnMixed = true; + + private final BatchedBeanControl beanControl; + + /** + * Create for a given transaction, PersistExecute, default size and + * getGeneratedKeys. + */ + public BatchControl(SpiTransaction t, int batchSize, boolean getGenKeys) { + this.transaction = t; + this.batchSize = batchSize; + this.getGeneratedKeys = getGenKeys; + this.beanControl = new BatchedBeanControl(t, this); + transaction.setBatchControl(this); + } + + /** + * Set this flag to false to allow batching of a mix of Beans and UpdateSql + * (or CallableSql). Normally if you mix the two this will result in an + * automatic flush. + *

+ * Note that UpdateSql and CallableSql will ALWAYS flush first. This is due to + * it already having been bound to a PreparedStatement where as the Beans go + * through a 2 step process when they are flushed (delayed binding). + *

+ */ + public void setBatchFlushOnMixed(boolean flushBatchOnMixed) { + this.batchFlushOnMixed = flushBatchOnMixed; + } + + /** + * Return the batchSize. + */ + public int getBatchSize() { + return batchSize; + } + + /** + * Set the size of batch execution. + *

+ * The user can set this via the Transaction. + *

+ */ + public void setBatchSize(int batchSize) { + if (batchSize > 1) { + this.batchSize = batchSize; + } + } + + /** + * Set whether or not to use getGeneratedKeys for this batch execution. + *

+ * The user can set this via the transaction + *

+ */ + public void setGetGeneratedKeys(Boolean getGeneratedKeys) { + if (getGeneratedKeys != null) { + this.getGeneratedKeys = getGeneratedKeys; + } + } + + /** + * Execute a Orm Update, SqlUpdate or CallableSql. + *

+ * These all go straight to jdbc and use addBatch(). Entity beans goto a queue + * and wait there so that the jdbc is executed in the correct order according + * to the depth. + *

+ */ + public int executeStatementOrBatch(PersistRequest request, boolean batch) { + if (!batch || (batchFlushOnMixed && !beanControl.isEmpty())) { + // flush when mixing beans and updateSql + flush(); + } + if (!batch) { + // execute the request immediately without batching + return request.executeNow(); + } + + if (pstmtHolder.getMaxSize() >= batchSize) { + flush(); + } + // for OrmUpdate, SqlUpdate, CallableSql there is no queue... + // so straight to jdbc prepared statement and use addBatch(). + // aka executeNow() may use addBatch(). + request.executeNow(); + return -1; + } + + /** + * Entity Bean insert, update or delete. This will either execute the request + * immediately or queue it for batch processing later. The queue is flushed + * according to the depth (object graph depth). + */ + public int executeOrQueue(PersistRequestBean request, boolean batch) { + + if (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty())) { + // flush when mixing beans and updateSql + flush(); + } + if (!batch) { + return request.executeNow(); + } + + // get the list we will add this request to + ArrayList persistList = beanControl.getPersistList(request); + if (persistList == null) { + // special case where the same bean instance has been added + // to the batch more than once + if (logger.isLoggable(Level.FINE)) { + logger.fine("Bean instance already in this batch: " + request.getBean()); + } + return -1; + } + + if (persistList.size() >= batchSize) { + // flush everything that has been batched + flush(); + + // we need to get the persistList again after the + // flush as the flush clears out the bean holders + persistList = beanControl.getPersistList(request); + } + + persistList.add(request); + return -1; + } + + /** + * Return the actual batch of PreparedStatements. + */ + public BatchedPstmtHolder getPstmtHolder() { + return pstmtHolder; + } + + /** + * Return true if the queue is empty. + */ + public boolean isEmpty() { + return (beanControl.isEmpty() && pstmtHolder.isEmpty()); + } + + /** + * Flush any batched PreparedStatements. + */ + protected void flushPstmtHolder() { + pstmtHolder.flush(getGeneratedKeys); + } + + /** + * Execute all the requests contained in the list. + */ + protected void executeNow(ArrayList list) { + for (int i = 0; i < list.size(); i++) { + list.get(i).executeNow(); + } + } + + /** + * execute all the requests currently queued or batched. + */ + public void flush() throws PersistenceException { + + if (!pstmtHolder.isEmpty()) { + // Flush existing pstmts (updateSql or callableSql) + flushPstmtHolder(); + } + if (beanControl.isEmpty()) { + // Nothing in queue to flush + return; + } + + // convert entry map to array for sorting + BatchedBeanHolder[] bsArray = beanControl.getArray(); + + // sort the entries by depth + Arrays.sort(bsArray, depthComparator); + + if (transaction.isLogSummary()) { + transaction.logInternal("BatchControl flush " + Arrays.toString(bsArray)); + } + for (int i = 0; i < bsArray.length; i++) { + BatchedBeanHolder bs = bsArray[i]; + bs.executeNow(); + // flush all the batched Pstmts + flushPstmtHolder(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java new file mode 100644 index 000000000..851be3c3d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * Used to sort BatchedBeanHolder by their depth. + *

+ * Beans are queued and put into BatchedBeanHolder along with their depth. This + * delays the actually binding to PreparedStatements until the + * BatchedBeanHolder's are flushed. This is so that we can get the generated + * keys from inserts. These values are required to persist the 'detail' beans. + *

+ */ +public class BatchDepthComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 264611821665757991L; + + public int compare(BatchedBeanHolder b1, BatchedBeanHolder b2) { + + if (b1.getOrder() < b2.getOrder()) { + return -1; + } + if (b1.getOrder() == b2.getOrder()) { + return 0; + } + return 1; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java new file mode 100644 index 000000000..f417ae469 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.sql.SQLException; + +/** + * Handles the processing required after batch execution. + *

+ * This includes concurrency checking, generated keys on inserts, transaction + * logging, transaction event table modifcation and for beans resetting their + * 'loaded' status. + *

+ */ +public interface BatchPostExecute { + + + /** + * Check that the rowCount is correct for this execute. This is for + * performing concurrency checking in batch execution. + */ + public void checkRowCount(int rowCount) throws SQLException; + + /** + * For inserts with generated keys. Otherwise not used. + */ + public void setGeneratedKey(Object idValue); + + /** + * Execute the post execute processing. + *

+ * This includes transaction logging, transaction event table modification + * and for beans resetting their 'loaded' status. + *

+ */ + public void postExecute() throws SQLException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java new file mode 100644 index 000000000..b734a3d36 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Holds all the batched beans. + *

+ * The beans are held here which delays the binding to a PreparedStatement. This + * 'delayed' binding is required as the beans need to be bound and executed in + * the correct order (according to the depth). + *

+ */ +public class BatchedBeanControl { + + /** + * Map of the BatchedBeanHolder objects. They each have a depth and are later + * sorted by their depth to get the execution order. + */ + private final HashMap beanHoldMap = new HashMap(); + + private final SpiTransaction transaction; + + private final BatchControl batchControl; + + private int topOrder; + + public BatchedBeanControl(SpiTransaction t, BatchControl batchControl) { + this.transaction = t; + this.batchControl = batchControl; + } + + public ArrayList getPersistList(PersistRequestBean request) { + return getBeanHolder(request).getList(request); + } + + /** + * Return an entry for the given type description. The type description is + * typically the bean class name (or table name for MapBeans). + */ + private BatchedBeanHolder getBeanHolder(PersistRequestBean request) { + + BeanDescriptor beanDescriptor = request.getBeanDescriptor(); + BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName()); + if (batchBeanHolder == null) { + int relativeDepth = transaction.depth(0); + if (relativeDepth == 0){ + topOrder++; + } + int stmtOrder = topOrder*100 + relativeDepth; + + batchBeanHolder = new BatchedBeanHolder(batchControl, beanDescriptor, stmtOrder); + beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder); + } + return batchBeanHolder; + } + + /** + * Return true if this holds no persist requests. + */ + public boolean isEmpty() { + return beanHoldMap.isEmpty(); + } + + /** + * Return the BatchedBeanHolder's ready for sorting and executing. + */ + public BatchedBeanHolder[] getArray() { + BatchedBeanHolder[] bsArray = new BatchedBeanHolder[beanHoldMap.size()]; + beanHoldMap.values().toArray(bsArray); + return bsArray; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java new file mode 100644 index 000000000..8e6cfde30 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java @@ -0,0 +1,157 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.HashSet; + +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Holds lists of persist requests for beans of a given typeDescription. + *

+ * This is used to delay the actual binding of the bean to PreparedStatements. + * The reason is that don't have all the bind values yet in the case of inserts + * with getGeneratedKeys. + *

+ *

+ * Has a depth which is used to determine the order in which it should be + * executed. The lowest depth is executed first. + *

+ */ +public class BatchedBeanHolder { + + /** + * The owning queue. + */ + private final BatchControl control; + + private final String shortDesc; + + /** + * The 'depth' which is used to determine the execution order. + */ + private final int order; + + /** + * The list of bean insert requests. + */ + private ArrayList inserts; + + /** + * The list of bean update requests. + */ + private ArrayList updates; + + /** + * The list of bean delete requests. + */ + private ArrayList deletes; + + private HashSet beanHashCodes = new HashSet(); + + /** + * Create a new entry with a given type and depth. + */ + public BatchedBeanHolder(BatchControl control, BeanDescriptor beanDescriptor, int order) { + this.control = control; + this.shortDesc = beanDescriptor.getName() + ":" + order; + this.order = order; + } + + /** + * Return the depth. + */ + public int getOrder() { + return order; + } + + /** + * Execute all the persist requests in this entry. + *

+ * This will Batch all the similar requests into one or more BatchStatements + * and then execute them. + *

+ */ + public void executeNow() { + // process the requests. Creates one or more PreparedStatements + // with binding addBatch() for each request. + + // Note updates and deletes can result in many PreparedStatements + // if their where clauses differ via use of IS NOT NULL. + if (inserts != null && !inserts.isEmpty()) { + control.executeNow(inserts); + inserts.clear(); + } + if (updates != null && !updates.isEmpty()) { + control.executeNow(updates); + updates.clear(); + } + if (deletes != null && !deletes.isEmpty()) { + control.executeNow(deletes); + deletes.clear(); + } + beanHashCodes.clear(); + } + + public String toString() { + return shortDesc; + } + + /** + * Return the list for the typeCode. + */ + public ArrayList getList(PersistRequestBean request) { + + Integer objHashCode = Integer.valueOf(System.identityHashCode(request.getBean())); + + if (!beanHashCodes.add(objHashCode)) { + // special case where the same bean instance has already been + // added to the batch (doesn't really occur with non-batching + // as the bean gets changed from dirty to loaded earlier) + return null; + } + + switch (request.getType()) { + case INSERT: + if (inserts == null) { + inserts = new ArrayList(); + } + return inserts; + + case UPDATE: + if (updates == null) { + updates = new ArrayList(); + } + return updates; + + case DELETE: + if (deletes == null) { + deletes = new ArrayList(); + } + return deletes; + + default: + throw new RuntimeException("Invalid type code " + request.getType()); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java new file mode 100644 index 000000000..231025fdd --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java @@ -0,0 +1,176 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import com.avaje.ebeaninternal.server.core.PstmtBatch; + +/** + * A batched statement that is held in BatchedPstmtHolder. It has a list of + * BatchPostExecute which it will process after the statement is executed. + *

+ * This can hold CallableStatements as well. + *

+ */ +public class BatchedPstmt { + + /** + * The underlying statement. + */ + private PreparedStatement pstmt; + + /** + * True if an insert that uses generated keys. + */ + private final boolean isGenKeys; + + /** + * The list of BatchPostExecute used to perform post processing. + */ + private final ArrayList list = new ArrayList(); + + private final String sql; + + private final PstmtBatch pstmtBatch; + + private final boolean occCheck; + + + /** + * Create with a given statement. + * @param isGenKeys true if an insert that uses generatedKeys + */ + public BatchedPstmt(PreparedStatement pstmt, boolean isGenKeys, String sql, PstmtBatch pstmtBatch, boolean occCheck) { + + this.pstmt = pstmt; + this.isGenKeys = isGenKeys; + this.sql = sql; + this.pstmtBatch = pstmtBatch; + this.occCheck = occCheck; + } + + /** + * Return the number of batched statements. + */ + public int size() { + return list.size(); + } + + /** + * Return the sql + */ + public String getSql() { + return sql; + } + + /** + * Return the statement. + */ + public PreparedStatement getStatement() { + return pstmt; + } + + /** + * Add the BatchPostExecute to the list for post execute processing. + */ + public void add(BatchPostExecute batchExecute){ + list.add(batchExecute); + } + + /** + * Execute the statement using executeBatch(). + * Run any post processing including getGeneratedKeys. + */ + public void executeBatch(boolean getGeneratedKeys) throws SQLException { + + executeAndCheckRowCounts(); + if (isGenKeys && getGeneratedKeys){ + getGeneratedKeys(); + } + postExecute(); + close(); + } + + /** + * Close the underlying statement. + */ + public void close() throws SQLException { + if (pstmt != null){ + pstmt.close(); + pstmt = null; + } + } + + private void postExecute() throws SQLException { + for (int i = 0; i < list.size(); i++) { + list.get(i).postExecute(); + } + } + + private void executeAndCheckRowCounts() throws SQLException { + + if (pstmtBatch != null){ + // oracle specific JDBC batch processing + int rc = pstmtBatch.executeBatch(pstmt, list.size(), sql, occCheck); + if (list.size() == 1){ + list.get(0).checkRowCount(rc); + } + // the optimistic concurrency row count check + // has already been done by pstmtBatch so just return + return; + + } + + // normal JDBC batch processing + int[] results = pstmt.executeBatch(); + + if (results.length != list.size()){ + String s = "results array error "+results.length+" "+list.size(); + throw new SQLException(s); + } + + // check for concurrency exceptions... + for (int i = 0; i < results.length; i++) { + list.get(i).checkRowCount(results[i]); + } + } + + private void getGeneratedKeys() throws SQLException { + + int index = 0; + ResultSet rset = pstmt.getGeneratedKeys(); + try { + while(rset.next()) { + Object idValue = rset.getObject(1); + list.get(index).setGeneratedKey(idValue); + index++; + } + } finally { + if (rset != null){ + rset.close(); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java new file mode 100644 index 000000000..6b2a6b6fb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +/** + * Used to hold BatchedPstmt objects for batch based execution. + *

+ * The BatchControl 'front ends' the batching by queuing the persist requests + * and ordering them according to depth and type. This object should only batch + * statements of a single 'depth' at any given time. + *

+ */ +public class BatchedPstmtHolder { + + private static final Logger logger = Logger.getLogger(BatchedPstmtHolder.class.getName()); + + /** + * A Map of the statements using a String key. This is used so that the same + * Statement,Prepared,Callable is reused. + */ + private LinkedHashMap stmtMap = new LinkedHashMap(); + + /** + * The Max size across all the BatchedPstmt. + */ + private int maxSize; + + public BatchedPstmtHolder() { + + } + + /** + * Return the PreparedStatement if it has already been used in this Batch. + * This will return null if no matching PreparedStatement is found. + */ + public PreparedStatement getStmt(String stmtKey, BatchPostExecute postExecute) { + BatchedPstmt bs = stmtMap.get(stmtKey); + if (bs == null) { + // the PreparedStatement has need been created + return null; + } + // add the post execute processing for this bean/row + bs.add(postExecute); + + // maintain a max batch size for any given batched stmt. + // Used to determine when to flush. + int bsSize = bs.size(); + if (bsSize > maxSize){ + maxSize = bsSize; + } + return bs.getStatement(); + } + + /** + * Add a new PreparedStatement wrapped in the BatchStatement object. + */ + public void addStmt(BatchedPstmt bs, BatchPostExecute postExecute) { + // add the batch post execute to the statement for POST processing + bs.add(postExecute); + + // cache so that getStmt() can find it for additional beans/rows + stmtMap.put(bs.getSql(), bs); + } + + /** + * Return true if the batch has no statements to execute. + */ + public boolean isEmpty() { + return stmtMap.isEmpty(); + } + + /** + * Execute all batched PreparedStatements. + * + * @param getGeneratedKeys + * if true try to get generated keys for inserts + */ + public void flush(boolean getGeneratedKeys) throws PersistenceException { + + SQLException firstError = null; + String errorSql = null; + + // flag set if something fails. Will not execute + // but still need to close PreparedStatements. + boolean isError = false; + + Iterator it = stmtMap.values().iterator(); + while (it.hasNext()) { + BatchedPstmt bs = it.next(); + try { + if (!isError) { + bs.executeBatch(getGeneratedKeys); + } + } catch (SQLException ex) { + SQLException next = ex.getNextException(); + while(next != null) { + logger.log(Level.SEVERE, "Next Exception during batch execution", next); + next = next.getNextException(); + } + + if (firstError == null) { + firstError = ex; + errorSql = bs.getSql(); + } else { + logger.log(Level.SEVERE, null, ex); + } + isError = true; + + } finally { + try { + bs.close(); + } catch (SQLException ex) { + // error closing PreparedStatement + logger.log(Level.SEVERE, null, ex); + } + } + } + + // clear the batch cache + stmtMap.clear(); + maxSize = 0; + + if (firstError != null) { + String msg = "Error when batch flush on sql: "+errorSql; + throw new PersistenceException(msg, firstError); + } + } + + /** + * Return the size of the biggest batched statement. + *

+ * Used to determine when to flush the batch. + *

+ */ + public int getMaxSize() { + return maxSize; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java new file mode 100644 index 000000000..70ca47857 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; + +/** + * Defines bean insert update and delete implementation. + */ +public interface BeanPersister { + + /** + * execute the insert bean request. + */ + public void insert(PersistRequestBean request) throws PersistenceException; + + /** + * execute the update bean request. + */ + public void update(PersistRequestBean request) throws PersistenceException; + + /** + * execute the delete bean request. + */ + public void delete(PersistRequestBean request) throws PersistenceException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java new file mode 100644 index 000000000..3cfe8d371 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Factory for creating BeanPersister implementations. + */ +public interface BeanPersisterFactory { + + /** + * Create the BeanPersister implemenation for a given type. + */ + public BeanPersister create(BeanDescriptor desc); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java new file mode 100644 index 000000000..ea733a9a8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; + +/** + * Holds a list of bind values for binding to a PreparedStatement. + */ +public class BindValues { + + int commentCount; + + final ArrayList list = new ArrayList(); + + /** + * Create with a Binder. + */ + public BindValues(){ + } + + /** + * Return the number of bind values. + */ + public int size() { + return list.size() - commentCount; + } + + /** + * Add a bind value with its JDBC datatype. + * + * @param value the bind value + * @param dbType the type as per java.sql.Types + */ + public void add(Object value, int dbType, String name){ + list.add(new Value(value, dbType, name)); + } + + public void addComment(String comment){ + ++commentCount; + list.add(new Value(comment)); + } + + /** + * List of bind values. + */ + public ArrayList values() { + return list; + } + + /** + * A Value has additionally the JDBC data type. + */ + public static class Value { + + private final Object value; + + private final int dbType; + + private final String name; + + private final boolean isComment; + + /** + * Create a comment. This is so that comments can be put into + * the bind log. + */ + public Value(String comment) { + this.name = comment; + this.isComment = true; + value = null; + dbType = 0; + } + + + /** + * Create the value. + */ + public Value(Object value, int dbType, String name) { + this.isComment = false; + this.value = value; + this.dbType = dbType; + this.name = name; + } + + /** + * This is a comment for the bind log and NOT an actual bind value. + */ + public boolean isComment() { + return isComment; + } + + /** + * Return the type as per java.sql.Types. + */ + public int getDbType() { + return dbType; + } + + /** + * Return the value. + */ + public Object getValue() { + return value; + } + + /** + * Return the property name. + */ + public String getName() { + return name; + } + + public String toString(){ + return ""+value; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java new file mode 100644 index 000000000..91b2dc2aa --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java @@ -0,0 +1,402 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.math.BigDecimal; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.TypeManager; + +/** + * Binds bean values to a PreparedStatement. + */ +public class Binder { + + private static final Logger logger = Logger.getLogger(Binder.class.getName()); + + //private final Calendar calendar; + + private final TypeManager typeManager; + + /** + * Set the PreparedStatement with which to bind variables to. + */ + public Binder(TypeManager typeManager) { + + this.typeManager = typeManager; + //this.calendar = new GregorianCalendar(); + } + + /** + * Bind the values to the Prepared Statement. + */ + public void bind(BindValues bindValues, DataBind dataBind, StringBuilder bindBuf) + throws SQLException { + + String logPrefix = ""; + + ArrayList list = bindValues.values(); + for (int i = 0; i < list.size(); i++) { + BindValues.Value bindValue = (BindValues.Value) list.get(i); + if (bindValue.isComment()) { + if (bindBuf != null) { + bindBuf.append(bindValue.getName()); + if (logPrefix.equals("")) { + logPrefix = ", "; + } + } + } else { + Object val = bindValue.getValue(); + int dt = bindValue.getDbType(); + bindObject(dataBind, val, dt); + + if (bindBuf != null) { + bindBuf.append(logPrefix); + if (logPrefix.equals("")) { + logPrefix = ", "; + } + bindBuf.append(bindValue.getName()); + bindBuf.append("="); + if (isLob(dt)) { + bindBuf.append("[LOB]"); + } else { + bindBuf.append(String.valueOf(val)); + } + } + } + } + } + + /** + * Bind the list of positionedParameters in BindParams. + */ + public String bind(BindParams bindParams, DataBind dataBind) + throws SQLException { + + StringBuilder bindLog = new StringBuilder(); + bind(bindParams, dataBind, bindLog); + return bindLog.toString(); + } + + /** + * Bind the list of positionedParameters in BindParams. + */ + public void bind(BindParams bindParams, DataBind dataBind, StringBuilder bindLog) + throws SQLException { + + bind(bindParams.positionedParameters(), dataBind, bindLog); + } + + /** + * Bind the list of parameters.. + */ + public void bind(List list, DataBind dataBind, StringBuilder bindLog) + throws SQLException { + + CallableStatement cstmt = null; + + if (dataBind.getPstmt() instanceof CallableStatement) { + cstmt = (CallableStatement) dataBind.getPstmt(); + } + + // the iterator is assumed to be in the correct order + Object value = null; + try { + for (int i = 0; i < list.size(); i++) { + + BindParams.Param param = list.get(i); + + if (param.isOutParam() && cstmt != null){ + cstmt.registerOutParameter(dataBind.nextPos(), param.getType()); + if (param.isInParam()) { + dataBind.decrementPos(); + } + } + if (param.isInParam()) { + value = param.getInValue(); + if (bindLog != null) { + if (param.isEncryptionKey()){ + bindLog.append("****"); + } else { + bindLog.append(value); + } + bindLog.append(", "); + } + if (value == null) { + // this doesn't work for query predicates + bindObject(dataBind, null, param.getType()); + } else { + bindObject(dataBind, value); + } + } + } + + } catch (SQLException ex) { + logger.warning(Message.msg("fetch.bind.error", "" + (dataBind.currentPos() - 1), value)); + throw ex; + } + } + + /** + * Bind an Object with unknown data type. + */ + public void bindObject(DataBind dataBind, Object value) throws SQLException { + + if (value == null) { + // null of unknown type + bindObject(dataBind, null, Types.OTHER); + + } else { + + ScalarType type = typeManager.getScalarType(value.getClass()); + if (type == null){ + // the type is not registered with the TypeManager. + String msg = "No ScalarType registered for "+value.getClass(); + throw new PersistenceException(msg); + + } else if (!type.isJdbcNative()) { + // convert to a JDBC native type + value = type.toJdbcType(value); + } + + int dbType = type.getJdbcType(); + bindObject(dataBind, value, dbType); + } + } + + /** + * bind a single value. + *

+ * Note that java.math.BigInteger is supported by converting it to a Long. + *

+ *

+ * Note if we get a java.util.Date or java.util.Calendar then these have + * been anonymously passed in (UpdateSql etc). There is a global setting to + * convert then to a java.sql.Date or java.sql.Timestamp for binding. The + * default is that both are converted to java.sql.Timestamp. + *

+ */ + public void bindObject(DataBind dataBind, Object data, int dbType) + throws SQLException { + + if (data == null){ + dataBind.setNull(dbType); + return; + } + + switch (dbType) { + case java.sql.Types.LONGVARCHAR: + bindLongVarChar(dataBind, data); + break; + + case java.sql.Types.LONGVARBINARY: + bindLongVarBinary(dataBind, data); + break; + + case java.sql.Types.CLOB: + bindClob(dataBind, data); + break; + + case java.sql.Types.BLOB: + bindBlob(dataBind, data); + break; + + default: + + bindSimpleData(dataBind, dbType, data); + } + } + + /** + * Binds the value to the statement according to the data type. + */ + private void bindSimpleData(DataBind b, int dataType, Object data) + throws SQLException { + + try { + switch (dataType) { + case java.sql.Types.BOOLEAN: + Boolean bo = (Boolean) data; + b.setBoolean(bo.booleanValue()); + break; + case java.sql.Types.BIT: + // Types.BIT should map to Java Boolean + Boolean bitBool = (Boolean) data; + b.setBoolean(bitBool.booleanValue()); + break; + + case java.sql.Types.VARCHAR: + b.setString((String) data); + break; + + case java.sql.Types.CHAR: + b.setString(data.toString()); + break; + + case java.sql.Types.TINYINT: + b.setByte(((Byte) data).byteValue()); + break; + + case java.sql.Types.SMALLINT: + b.setShort(((Short) data).shortValue()); + break; + + case java.sql.Types.INTEGER: + b.setInt(((Integer) data).intValue()); + break; + + case java.sql.Types.BIGINT: + b.setLong(((Long) data).longValue()); + break; + + case java.sql.Types.REAL: + b.setFloat(((Float) data).floatValue()); + break; + + case java.sql.Types.FLOAT: + // DB Float in theory maps to Java Double type + b.setDouble(((Double) data).doubleValue()); + break; + + case java.sql.Types.DOUBLE: + b.setDouble(((Double) data).doubleValue()); + break; + + case java.sql.Types.NUMERIC: + b.setBigDecimal((BigDecimal) data); + break; + + case java.sql.Types.DECIMAL: + b.setBigDecimal((BigDecimal) data); + break; + + case java.sql.Types.TIME: + //pstmt.setTime(index, (java.sql.Time) data, calendar); + b.setTime((java.sql.Time) data); + break; + + case java.sql.Types.DATE: + //pstmt.setDate(index, (java.sql.Date) data, calendar); + b.setDate((java.sql.Date) data); + break; + + case java.sql.Types.TIMESTAMP: + //pstmt.setTimestamp(index, (java.sql.Timestamp) data, calendar); + b.setTimestamp((java.sql.Timestamp) data); + break; + + case java.sql.Types.BINARY: + b.setBytes((byte[]) data); + break; + + case java.sql.Types.VARBINARY: + b.setBytes((byte[]) data); + break; + + case java.sql.Types.OTHER: + b.setObject(data); + break; + + case java.sql.Types.JAVA_OBJECT: + // Not too sure about this. + b.setObject(data); + break; + + default: + String msg = Message.msg("persist.bind.datatype", "" + dataType, "" + b.currentPos()); + throw new SQLException(msg); + } + + } catch (Exception e) { + String dataClass = "Data is null?"; + if (data != null) { + dataClass = data.getClass().getName(); + } + String m = "Error with property[" + b.currentPos() + "] dt[" + dataType + "]"; + m += "data[" + data + "][" + dataClass + "]"; + throw new PersistenceException(m, e); + } + } + + /** + * Bind String data to a LONGVARCHAR column. + */ + private void bindLongVarChar(DataBind b, Object data) + throws SQLException { + + String sd = (String) data; + b.setClob(sd); + } + + /** + * Bind byte[] data to a LONGVARBINARY column. + */ + private void bindLongVarBinary(DataBind b, Object data) + throws SQLException { + + byte[] bytes = (byte[]) data; + b.setBlob(bytes); + } + + /** + * Bind String data to a CLOB column. + */ + private void bindClob(DataBind b, Object data) throws SQLException { + + String sd = (String) data; + b.setClob(sd); + } + + /** + * Bind byte[] data to a BLOB column. + */ + private void bindBlob(DataBind b, Object data) throws SQLException { + + byte[] bytes = (byte[]) data; + b.setBlob(bytes); + } + + private boolean isLob(int dbType) { + switch (dbType) { + case Types.CLOB: + return true; + case Types.LONGVARCHAR: + return true; + case Types.BLOB: + return true; + case Types.LONGVARBINARY: + return true; + + default: + return false; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java new file mode 100644 index 000000000..f6a2fd21c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +/** + * Contants used in persist. + */ +public interface Constant { + + /** + * An INSERT clause. + */ + public static final int IN_INSERT = 1; + + /** + * An UPDATE SET clause. + */ + public static final int IN_UPDATE_SET = 2; + + /** + * An UPDATE WHERE clause. + */ + public static final int IN_UPDATE_WHERE = 3; + + /** + * A DELETE WHERE clause. + */ + public static final int IN_DELETE_WHERE = 4; +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java new file mode 100644 index 000000000..760228e77 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java @@ -0,0 +1,154 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.deploy.BeanManager; + +/** + * Default PersistExecute implementation using DML statements. + *

+ * Supports the use of PreparedStatement batching. + *

+ */ +public final class DefaultPersistExecute implements PersistExecute { + + private final ExeCallableSql exeCallableSql; + + private final ExeUpdateSql exeUpdateSql; + + private final ExeOrmUpdate exeOrmUpdate; + + /** + * The default batch size. + */ + private final int defaultBatchSize; + + /** + * Default for whether to call getGeneratedKeys after batch insert. + */ + private final boolean defaultBatchGenKeys; + + private final boolean validate; + + /** + * Construct this DmlPersistExecute. + */ + public DefaultPersistExecute(boolean validate, Binder binder, PstmtBatch pstmtBatch) { + + this.validate = validate; + this.exeOrmUpdate = new ExeOrmUpdate(binder, pstmtBatch); + this.exeUpdateSql = new ExeUpdateSql(binder, pstmtBatch); + this.exeCallableSql = new ExeCallableSql(binder, pstmtBatch); + + this.defaultBatchGenKeys = GlobalProperties.getBoolean("batch.getgeneratedkeys", true); + this.defaultBatchSize = GlobalProperties.getInt("batch.size", 20); + } + + public BatchControl createBatchControl(SpiTransaction t) { + + // create a BatchControl and set its defaults + return new BatchControl(t, defaultBatchSize, defaultBatchGenKeys); + } + + /** + * execute the bean insert request. + */ + public void executeInsertBean(PersistRequestBean request) { + + BeanManager mgr = request.getBeanManager(); + BeanPersister persister = mgr.getBeanPersister(); + + BeanPersistController controller = request.getBeanController(); + if (controller == null || controller.preInsert(request)) { + if (validate){ + request.validate(); + } + persister.insert(request); + // NOTE: the persister fires the postInsert so that this + // occurs before ebeanIntercept.setLoaded(true) + } + } + + /** + * execute the bean update request. + */ + public void executeUpdateBean(PersistRequestBean request) { + + BeanManager mgr = request.getBeanManager(); + BeanPersister persister = mgr.getBeanPersister(); + + BeanPersistController controller = request.getBeanController(); + if (controller == null || controller.preUpdate(request)) { + if (validate){ + request.validate(); + } + persister.update(request); + // NOTE: the persister fires the postUpdate so that this + // occurs before ebeanIntercept.setLoaded(true) + } + } + + + /** + * execute the bean delete request. + */ + public void executeDeleteBean(PersistRequestBean request) { + + BeanManager mgr = request.getBeanManager(); + BeanPersister persister = mgr.getBeanPersister(); + + BeanPersistController controller = request.getBeanController(); + if (controller == null || controller.preDelete(request)) { + + persister.delete(request); + // NOTE: the persister fires the postDelete + } + } + + /** + * Execute the updateSqlRequest + */ + public int executeOrmUpdate(PersistRequestOrmUpdate request) { + return exeOrmUpdate.execute(request); + } + + /** + * Execute the updateSqlRequest + */ + public int executeSqlUpdate(PersistRequestUpdateSql request) { + return exeUpdateSql.execute(request); + } + + /** + * Execute the CallableSqlRequest. + */ + public int executeSqlCallable(PersistRequestCallableSql request) { + return exeCallableSql.execute(request); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java new file mode 100644 index 000000000..73394d5db --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java @@ -0,0 +1,1390 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.CallableSql; +import com.avaje.ebean.Query; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.Update; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.config.ldap.LdapContextFactory; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiUpdate; +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; +import com.avaje.ebeaninternal.server.core.Persister; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeaninternal.server.deploy.BeanManager; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.IntersectionRow; +import com.avaje.ebeaninternal.server.deploy.ManyType; +import com.avaje.ebeaninternal.server.ldap.DefaultLdapPersister; +import com.avaje.ebeaninternal.server.ldap.LdapPersistBeanRequest; + +/** + * Persister implementation using DML. + *

+ * This object uses DmlPersistExecute to perform the actual persist execution. + *

+ *

+ * This object: + *

    + *
  • Determines insert or update for saved beans
  • + *
  • Determines the concurrency mode
  • + *
  • Handles cascading of save and delete
  • + *
  • Handles the batching and queueing
  • + *

    + * + * @see com.avaje.ebeaninternal.server.persist.DefaultPersistExecute + */ +public final class DefaultPersister implements Persister { + + private static final Logger logger = Logger.getLogger(DefaultPersister.class.getName()); + + /** + * Actually does the persisting work. + */ + private final PersistExecute persistExecute; + + private final DefaultLdapPersister ldapPersister; + + private final SpiEbeanServer server; + + private final BeanDescriptorManager beanDescriptorManager; + + private final boolean defaultUpdateNullProperties; + private final boolean defaultDeleteMissingChildren; + + public DefaultPersister(SpiEbeanServer server, boolean validate, + Binder binder, BeanDescriptorManager descMgr, PstmtBatch pstmtBatch, LdapContextFactory contextFactory) { + + this.server = server; + this.beanDescriptorManager = descMgr; + + this.persistExecute = new DefaultPersistExecute(validate, binder, pstmtBatch); + this.ldapPersister = new DefaultLdapPersister(contextFactory); + + this.defaultUpdateNullProperties = server.isDefaultUpdateNullProperties(); + this.defaultDeleteMissingChildren = server.isDefaultDeleteMissingChildren(); + } + + /** + * Execute the CallableSql. + */ + public int executeCallable(CallableSql callSql, Transaction t) { + + PersistRequestCallableSql request = new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute); + try { + request.initTransIfRequired(); + int rc = request.executeOrQueue(); + request.commitTransIfRequired(); + return rc; + + } catch (RuntimeException e) { + request.rollbackTransIfRequired(); + throw e; + } + } + + /** + * Execute the orm update. + */ + public int executeOrmUpdate(Update update, Transaction t) { + + SpiUpdate ormUpdate = (SpiUpdate) update; + + BeanManager mgr = beanDescriptorManager.getBeanManager(ormUpdate.getBeanType()); + + if (mgr == null) { + String msg = "No BeanManager found for type [" + ormUpdate.getBeanType() + "]. Is it an entity?"; + throw new PersistenceException(msg); + } + + PersistRequestOrmUpdate request = new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute); + try { + request.initTransIfRequired(); + int rc = request.executeOrQueue(); + request.commitTransIfRequired(); + return rc; + + } catch (RuntimeException e) { + request.rollbackTransIfRequired(); + throw e; + } + } + + /** + * Execute the updateSql. + */ + public int executeSqlUpdate(SqlUpdate updSql, Transaction t) { + + PersistRequestUpdateSql request = new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute); + try { + request.initTransIfRequired(); + int rc = request.executeOrQueue(); + request.commitTransIfRequired(); + return rc; + + } catch (RuntimeException e) { + request.rollbackTransIfRequired(); + throw e; + } + } + + /** + * Recursively delete the bean. This calls back to the EbeanServer. + */ + private void deleteRecurse(Object detailBean, Transaction t) { + // NB: a new PersistRequest is made + server.delete(detailBean, t); + } + + /** + * Force an Update using the given bean. + */ + public void forceUpdate(Object bean, Set updateProps, Transaction t, boolean deleteMissingChildren, boolean updateNullProperties) { + + if (bean == null) { + throw new NullPointerException(Message.msg("bean.isnull")); + } + + if (updateProps == null) { + // checking to see if this is just a 'normal' update + if (bean instanceof EntityBean) { + EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept(); + if (ebi.isDirty() || ebi.isLoaded()) { + // a 'normal' update using 'dirty' properties from internal bean state. + // if not dirty we still update in case any cascading save occurs + PersistRequestBean req = createRequest(bean, t, null); + try { + req.initTransIfRequired(); + update(req); + req.commitTransIfRequired(); + // finished a 'normal' update + return; + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } else if (ebi.isReference()) { + // just return as no point in cascading (no modified beans/lists) + return; + } + + // loadedProps set by Ebean JSON / XML Marshalling + updateProps = ebi.getLoadedProps(); + } + } + + BeanManager mgr = getBeanManager(bean); + if (mgr == null) { + throw new PersistenceException(errNotRegistered(bean.getClass())); + } + + forceUpdateStateless(bean, t, null, mgr, updateProps, deleteMissingChildren, updateNullProperties); + } + + /** + * Force a 'stateless' update determining which properties to update. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void forceUpdateStateless(Object bean, Transaction t, Object parentBean, BeanManager mgr, Set updateProps, + boolean deleteMissingChildren, boolean updateNullProperties) { + + BeanDescriptor descriptor = mgr.getBeanDescriptor(); + + // determine concurrency mode based on version property not null + ConcurrencyMode mode = descriptor.determineConcurrencyMode(bean); + + if (updateProps == null) { + // determine based on null treatment (all properties updated or just the non-null ones) + updateProps = updateNullProperties ? null : descriptor.determineLoadedProperties(bean); + + } else if (updateProps.isEmpty()) { + // in this case means we want to include all properties in the update + updateProps = null; + + } else if (ConcurrencyMode.VERSION.equals(mode)) { + // check that the version property is included + String verName = descriptor.firstVersionProperty().getName(); + if (!updateProps.contains(verName)) { + // defensively copy the updateProps and add the version property name + updateProps = new HashSet(updateProps); + updateProps.add(verName); + } + } + + PersistRequestBean req; + if (descriptor.isLdapEntityType()) { + req = new LdapPersistBeanRequest(server, bean, parentBean, mgr, ldapPersister, updateProps, mode); + + } else { + // special constructor for force 'stateless' Update mode ... + req = new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, updateProps, mode); + req.setStatelessUpdate(true, deleteMissingChildren, updateNullProperties); + } + + try { + req.initTransIfRequired(); + update(req); + req.commitTransIfRequired(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + public void save(Object bean, Transaction t) { + saveRecurse(bean, t, null); + } + + /** + * Explicitly specify to insert this bean. + */ + public void forceInsert(Object bean, Transaction t) { + + PersistRequestBean req = createRequest(bean, t, null); + try { + req.initTransIfRequired(); + insert(req); + req.commitTransIfRequired(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + private void saveRecurse(Object bean, Transaction t, Object parentBean) { + if (bean == null) { + throw new NullPointerException(Message.msg("bean.isnull")); + } + + if (bean instanceof EntityBean == false) { + saveVanillaRecurse(bean, t, parentBean); + return; + } + + PersistRequestBean req = createRequest(bean, t, parentBean); + try { + req.initTransIfRequired(); + saveEnhanced(req); + req.commitTransIfRequired(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + /** + * Insert or update the bean depending on PersistControl and the bean state. + */ + private void saveEnhanced(PersistRequestBean request) { + + EntityBeanIntercept intercept = request.getEntityBeanIntercept(); + + if (intercept.isReference()) { + // its a reference... + if (request.isPersistCascade()) { + // save any associated List held beans + intercept.setLoaded(); + saveAssocMany(false, request); + intercept.setReference(); + } + + } else { + if (intercept.isLoaded()) { + // Need to call setLoaded(false) to simulate insert + update(request); + } else { + insert(request); + } + } + } + + /** + * Determine if this is an Insert or update for the 'vanilla' bean. + */ + private void saveVanillaRecurse(Object bean, Transaction t, Object parentBean) { + + BeanManager mgr = getBeanManager(bean); + if (mgr == null) { + throw new RuntimeException("No Mgr found for " + bean + " " + bean.getClass()); + } + // use the version property to determine insert or update + if (mgr.getBeanDescriptor().isVanillaInsert(bean)) { + saveVanillaInsert(bean, t, parentBean, mgr); + + } else { + // update non-null properties (no partial object knowledge with vanilla bean) + forceUpdateStateless(bean, t, parentBean, mgr, null, defaultDeleteMissingChildren, defaultUpdateNullProperties); + } + } + + /** + * Perform insert on non-enhanced bean (effectively same as enhanced bean). + */ + private void saveVanillaInsert(Object bean, Transaction t, Object parentBean, BeanManager mgr) { + + PersistRequestBean req = createRequest(bean, t, parentBean, mgr); + try { + req.initTransIfRequired(); + insert(req); + req.commitTransIfRequired(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + /** + * Insert the bean. + */ + private void insert(PersistRequestBean request) { + + if (request.isRegisteredBean()){ + // skip as already inserted/updated in this request (recursive cascading) + return; + } + + try { + request.setType(PersistRequest.Type.INSERT); + + if (request.isPersistCascade()) { + // save associated One beans recursively first + saveAssocOne(request); + } + + // set the IDGenerated value if required + setIdGenValue(request); + request.executeOrQueue(); + + if (request.isPersistCascade()) { + // save any associated List held beans + saveAssocMany(true, request); + } + } finally { + request.unRegisterBean(); + } + } + + /** + * Update the bean. Return NOT_SAVED if the bean values have not changed. + */ + private void update(PersistRequestBean request) { + + if (request.isRegisteredBean()){ + // skip as already inserted/updated in this request (recursive cascading) + return; + } + + try { + // we have determined that it is an update + request.setType(PersistRequest.Type.UPDATE); + if (request.isPersistCascade()) { + // save associated One beans recursively first + saveAssocOne(request); + } + + if (request.isDirty()) { + request.executeOrQueue(); + + } else { + // skip validation on unchanged bean + if (logger.isLoggable(Level.FINE)) { + logger.fine(Message.msg("persist.update.skipped", request.getBean())); + } + } + + if (request.isPersistCascade()) { + // save all the beans in assocMany's after + saveAssocMany(false, request); + } + } finally { + request.unRegisterBean(); + } + } + + /** + * Delete the bean with the explicit transaction. + */ + public void delete(Object bean, Transaction t) { + + PersistRequestBean req = createRequest(bean, t, null); + if (req.isRegisteredForDeleteBean()) { + // skip deleting bean. Used where cascade is on + // both sides of a relationship + if (logger.isLoggable(Level.FINE)) { + logger.fine("skipping delete on alreadyRegistered " + bean); + } + return; + } + req.setType(PersistRequest.Type.DELETE); + try { + req.initTransIfRequired(); + delete(req); + req.commitTransIfRequired(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + private void deleteList(List beanList, Transaction t) { + for (int i = 0; i < beanList.size(); i++) { + Object bean = beanList.get(i); + delete(bean, t); + } + } + + /** + * Delete by a List of Id's. + */ + public void deleteMany(Class beanType, Collection ids, Transaction transaction) { + + if (ids == null || ids.size() == 0) { + return; + } + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(beanType); + + ArrayList idList = new ArrayList(ids.size()); + for (Object id : ids) { + // convert to appropriate type if required + idList.add(descriptor.convertId(id)); + } + + delete(descriptor, null, idList, transaction); + } + + /** + * Delete by Id. + */ + public int delete(Class beanType, Object id, Transaction transaction) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(beanType); + + // convert to appropriate type if required + id = descriptor.convertId(id); + return delete(descriptor, id, null, transaction); + } + + /** + * Delete by Id or a List of Id's. + */ + private int delete(BeanDescriptor descriptor, Object id, List idList, Transaction transaction) { + + SpiTransaction t = (SpiTransaction) transaction; + if (t.isPersistCascade()) { + BeanPropertyAssocOne[] propImportDelete = descriptor.propertiesOneImportedDelete(); + if (propImportDelete.length > 0) { + // We actually need to execute a query to get the foreign key values + // as they are required for the delete cascade. Query back just the + // Id and the appropriate foreign key values + Query q = deleteRequiresQuery(descriptor, propImportDelete); + if (idList != null) { + q.where().idIn(idList); + if (t.isLogSummary()) { + t.logInternal("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values"); + } + List beanList = server.findList(q, t); + deleteList(beanList, t); + return beanList.size(); + + } else { + q.where().idEq(id); + if (t.isLogSummary()) { + t.logInternal("-- DeleteById of " + descriptor.getName() + " id[" + id + "] requires fetch of foreign key values"); + } + Object bean = server.findUnique(q, t); + if (bean == null) { + return 0; + } else { + delete(bean, t); + return 1; + } + } + } + } + + if (t.isPersistCascade()) { + // OneToOne exported side with delete cascade + BeanPropertyAssocOne[] expOnes = descriptor.propertiesOneExportedDelete(); + for (int i = 0; i < expOnes.length; i++) { + BeanDescriptor targetDesc = expOnes[i].getTargetDescriptor(); + if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isUsingL2Cache()) { + SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList); + executeSqlUpdate(sqlDelete, t); + } else { + List childIds = expOnes[i].findIdsByParentId(id, idList, t); + delete(targetDesc, null, childIds, t); + } + } + + // OneToMany's with delete cascade + BeanPropertyAssocMany[] manys = descriptor.propertiesManyDelete(); + for (int i = 0; i < manys.length; i++) { + BeanDescriptor targetDesc = manys[i].getTargetDescriptor(); + if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isUsingL2Cache()) { + // we can just delete children with a single statement + SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList); + executeSqlUpdate(sqlDelete, t); + } else { + // we need to fetch the Id's to delete (recurse or notify L2 cache/lucene) + List childIds = manys[i].findIdsByParentId(id, idList, t, null); + delete(targetDesc, null, childIds, t); + } + } + } + + // ManyToMany's ... delete from intersection table + BeanPropertyAssocMany[] manys = descriptor.propertiesManyToMany(); + for (int i = 0; i < manys.length; i++) { + SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList); + if (t.isLogSummary()) { + t.logInternal("-- Deleting intersection table entries: " + manys[i].getFullBeanName()); + } + executeSqlUpdate(sqlDelete, t); + } + + // delete the bean(s) + SqlUpdate deleteById = descriptor.deleteById(id, idList); + if (t.isLogSummary()) { + t.logInternal("-- Deleting " + descriptor.getName() + " Ids" + idList); + } + + // use Id's to update L2 cache rather than Bulk table event + deleteById.setAutoTableMod(false); + if (idList != null) { + t.getEvent().addDeleteByIdList(descriptor, idList); + } else { + t.getEvent().addDeleteById(descriptor, id); + } + return executeSqlUpdate(deleteById, t); + } + + /** + * We need to create and execute a query to get the foreign key values as + * the delete cascades to them (foreign keys). + */ + private Query deleteRequiresQuery(BeanDescriptor desc, BeanPropertyAssocOne[] propImportDelete) { + + Query q = server.createQuery(desc.getBeanType()); + StringBuilder sb = new StringBuilder(30); + for (int i = 0; i < propImportDelete.length; i++) { + sb.append(propImportDelete[i].getName()).append(","); + } + q.setAutofetch(false); + q.select(sb.toString()); + return q; + } + + /** + * Delete the bean. + *

    + * Note that preDelete fires before the deletion of children. + *

    + */ + private void delete(PersistRequestBean request) { + + DeleteUnloadedForeignKeys unloadedForeignKeys = null; + +// boolean pauseIndexInvalidate = request.getBeanDescriptor().isLuceneIndexed(); +// if (pauseIndexInvalidate) { +// // Stop any deletions of child beans to Invalidate the Index as we +// // will use the delete of this top level object to maintain the index +// request.pauseIndexInvalidate(); +// } + + if (request.isPersistCascade()) { + // delete children first ... register the + // bean to handle bi-directional cascading + request.registerDeleteBean(); + deleteAssocMany(request); + request.unregisterDeleteBean(); + + unloadedForeignKeys = getDeleteUnloadedForeignKeys(request); + if (unloadedForeignKeys != null) { + // there are foreign keys that we don't have on this partially + // populated bean so we actually need to query them (to cascade delete) + unloadedForeignKeys.queryForeignKeys(); + } + } + + request.executeOrQueue(); + + if (request.isPersistCascade()) { + deleteAssocOne(request); + + if (unloadedForeignKeys != null) { + unloadedForeignKeys.deleteCascade(); + } + } +// if (pauseIndexInvalidate) { +// request.resumeIndexInvalidate(); +// } + } + + /** + * Save the associated child beans contained in a List. + *

    + * This will automatically copy over any join properties from the parent + * bean to the child beans. + *

    + */ + private void saveAssocMany(boolean insertedParent, PersistRequestBean request) { + + Object parentBean = request.getBean(); + BeanDescriptor desc = request.getBeanDescriptor(); + SpiTransaction t = request.getTransaction(); + + // exported ones with cascade save + BeanPropertyAssocOne[] expOnes = desc.propertiesOneExportedSave(); + for (int i = 0; i < expOnes.length; i++) { + BeanPropertyAssocOne prop = expOnes[i]; + + // check for partial beans + if (request.isLoadedProperty(prop)) { + Object detailBean = prop.getValue(parentBean); + if (detailBean != null) { + if (prop.isSaveRecurseSkippable(detailBean)) { + // skip saving this bean + } else { + t.depth(+1); + saveRecurse(detailBean, t, parentBean); + t.depth(-1); + } + } + } + } + + // many's with cascade save + BeanPropertyAssocMany[] manys = desc.propertiesManySave(); + for (int i = 0; i < manys.length; i++) { + saveMany(new SaveManyPropRequest(insertedParent, manys[i], parentBean, request)); + } + } + + /** + * Helper to wrap the details when saving a OneToMany or ManyToMany + * relationship. + */ + private static class SaveManyPropRequest { + private final boolean insertedParent; + private final BeanPropertyAssocMany many; + private final Object parentBean; + private final SpiTransaction t; + private final boolean cascade; + private final boolean statelessUpdate; + private final boolean deleteMissingChildren; + private final boolean updateNullProperties; + + private SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany many, Object parentBean, PersistRequestBean request) { + this.insertedParent = insertedParent; + this.many = many; + this.cascade = many.getCascadeInfo().isSave(); + this.parentBean = parentBean; + this.t = request.getTransaction(); + this.statelessUpdate = request.isStatelessUpdate(); + this.deleteMissingChildren = request.isDeleteMissingChildren(); + this.updateNullProperties = request.isUpdateNullProperties(); + } + + private SaveManyPropRequest(BeanPropertyAssocMany many, Object parentBean, SpiTransaction t) { + this.insertedParent = false; + this.many = many; + this.parentBean = parentBean; + this.t = t; + this.cascade = true; + this.statelessUpdate = false; + this.deleteMissingChildren = false; + this.updateNullProperties = false; + } + + private Object getValueUnderlying() { + return many.getValueUnderlying(parentBean); + } + + private boolean isModifyListenMode() { + return ModifyListenMode.REMOVALS.equals(many.getModifyListenMode()); + } + + private boolean isStatelessUpdate() { + return statelessUpdate; + } + + private boolean isDeleteMissingChildren() { + return deleteMissingChildren; + } + + private boolean isUpdateNullProperties() { + return updateNullProperties; + } + + private boolean isInsertedParent() { + return insertedParent; + } + + private BeanPropertyAssocMany getMany() { + return many; + } + + private Object getParentBean() { + return parentBean; + } + + private SpiTransaction getTransaction() { + return t; + } + + private boolean isCascade() { + return cascade; + } + } + + private void saveMany(SaveManyPropRequest saveMany) { + + if (saveMany.getMany().isManyToMany()) { + // save the beans that are in the manyToMany + if (saveMany.isCascade()) { + // Need explicit Cascade to save the beans on other side + saveAssocManyDetails(saveMany, false, saveMany.isUpdateNullProperties()); + // for ManyToMany save the 'relationship' via inserts/deletes + // into/from the intersection table + saveAssocManyIntersection(saveMany, saveMany.isDeleteMissingChildren()); + } + + } else { + if (saveMany.isCascade()) { + saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren(), saveMany.isUpdateNullProperties()); + } + if (saveMany.isModifyListenMode()) { + removeAssocManyPrivateOwned(saveMany); + } + } + } + + private void removeAssocManyPrivateOwned(SaveManyPropRequest saveMany) { + + Object details = saveMany.getValueUnderlying(); + + // check that the list is not null and if it is a BeanCollection + // check that is has been populated (don't trigger lazy loading) + if (details instanceof BeanCollection) { + + BeanCollection c = (BeanCollection) details; + Set modifyRemovals = c.getModifyRemovals(); + if (modifyRemovals != null && !modifyRemovals.isEmpty()) { + + SpiTransaction t = saveMany.getTransaction(); + // increase depth for batching order + t.depth(+1); + for (Object removedBean : modifyRemovals) { + deleteRecurse(removedBean, t); + } + t.depth(-1); + } + } + } + + /** + * Save the details from a OneToMany collection. + */ + private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren, boolean updateNullProperties) { + + BeanPropertyAssocMany prop = saveMany.getMany(); + + Object details = saveMany.getValueUnderlying(); + + // check that the list is not null and if it is a BeanCollection + // check that is has been populated (don't trigger lazy loading) + Collection collection = getDetailsIterator(details); + + if (collection == null) { + // nothing to do here + return; + } + + if (saveMany.isInsertedParent()) { + // performance optimisation for large collections + prop.getTargetDescriptor().preAllocateIds(collection.size()); + } + + BeanDescriptor targetDescriptor = prop.getTargetDescriptor(); + ArrayList detailIds = null; + if (deleteMissingChildren) { + // collect the Id's (to exclude from deleteManyDetails) + detailIds = new ArrayList(); + } + + // increase depth for batching order + SpiTransaction t = saveMany.getTransaction(); + t.depth(+1); + + // if a map, then we get the key value and + // set it to the appropriate property on the + // detail bean before we save it + boolean isMap = ManyType.JAVA_MAP.equals(prop.getManyType()); + Object parentBean = saveMany.getParentBean(); + Object mapKeyValue = null; + + boolean saveSkippable = prop.isSaveRecurseSkippable(); + boolean skipSavingThisBean = false; + + for (Object detailBean : collection) { + if (isMap) { + // its a map so need the key and value + Map.Entry entry = (Map.Entry) detailBean; + mapKeyValue = entry.getKey(); + detailBean = entry.getValue(); + } + + if (prop.isManyToMany()) { + if (detailBean instanceof EntityBean) { + skipSavingThisBean = ((EntityBean) detailBean)._ebean_getIntercept().isReference(); + } + } else { + // set the 'parent/master' bean to the detailBean as long + // as we don't make it 'dirty' in doing so + if (detailBean instanceof EntityBean) { + EntityBeanIntercept ebi = ((EntityBean) detailBean)._ebean_getIntercept(); + if (ebi.isNewOrDirty()) { + // set the parent bean to detailBean + prop.setJoinValuesToChild(parentBean, detailBean, mapKeyValue); + } else if (ebi.isReference()) { + // we can skip this one + skipSavingThisBean = true; + + } else { + // unmodified so skip depending on prop.isSaveRecurseSkippable(); + skipSavingThisBean = saveSkippable; + } + } else { + // set the parent bean to detailBean + prop.setJoinValuesToChild(parentBean, detailBean, mapKeyValue); + } + } + + if (skipSavingThisBean) { + // unmodified bean that does not recurse its save + // so we can skip the save for this bean. + // Reset skipSavingThisBean for the next detailBean + skipSavingThisBean = false; + + } else if (!saveMany.isStatelessUpdate()) { + // normal save recurse + saveRecurse(detailBean, t, parentBean); + + } else { + if (targetDescriptor.isStatelessUpdate(detailBean)) { + // update based on the value of Version/Id properties + // cascade update in stateless mode + forceUpdate(detailBean, null, t, deleteMissingChildren, updateNullProperties); + } else { + // cascade insert + forceInsert(detailBean, t); + } + } + + if (detailIds != null) { + // remember the Id (other details not in the collection) will be removed + Object id = targetDescriptor.getId(detailBean); + if (!DmlUtil.isNullOrZero(id)) { + detailIds.add(id); + } + } + } + + if (detailIds != null) { + deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds); + } + + t.depth(-1); + + } + + public int deleteManyToManyAssociations(Object ownerBean, String propertyName, Transaction t) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass()); + BeanPropertyAssocMany prop = (BeanPropertyAssocMany) descriptor.getBeanProperty(propertyName); + return deleteAssocManyIntersection(ownerBean, prop, t); + } + + public void saveManyToManyAssociations(Object ownerBean, String propertyName, Transaction t) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass()); + BeanPropertyAssocMany prop = (BeanPropertyAssocMany) descriptor.getBeanProperty(propertyName); + + saveAssocManyIntersection(new SaveManyPropRequest(prop, ownerBean, (SpiTransaction) t), false); + } + + public void saveAssociation(Object parentBean, String propertyName, Transaction t) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(parentBean.getClass()); + SpiTransaction trans = (SpiTransaction) t; + + BeanProperty prop = descriptor.getBeanProperty(propertyName); + if (prop == null) { + String msg = "Could not find property [" + propertyName + "] on bean " + parentBean.getClass(); + throw new PersistenceException(msg); + } + + if (prop instanceof BeanPropertyAssocMany) { + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) prop; + saveMany(new SaveManyPropRequest(manyProp, parentBean, (SpiTransaction) t)); + + } else if (prop instanceof BeanPropertyAssocOne) { + BeanPropertyAssocOne oneProp = (BeanPropertyAssocOne) prop; + Object assocBean = oneProp.getValue(parentBean); + + int depth = oneProp.isOneToOneExported() ? 1 : -1; + int revertDepth = -1 * depth; + + trans.depth(depth); + saveRecurse(assocBean, t, parentBean); + trans.depth(revertDepth); + + } else { + String msg = "Expecting [" + prop.getFullBeanName() + "] to be a OneToMany, OneToOne, ManyToOne or ManyToMany property?"; + throw new PersistenceException(msg); + } + + } + + /** + * Save the additions and removals from a ManyToMany collection as inserts + * and deletes from the intersection table. + *

    + * This is done via MapBeans. + *

    + */ + private void saveAssocManyIntersection(SaveManyPropRequest saveManyPropRequest, boolean deleteMissingChildren) { + + BeanPropertyAssocMany prop = saveManyPropRequest.getMany(); + Object value = prop.getValueUnderlying(saveManyPropRequest.getParentBean()); + if (value == null) { + return; + } + + SpiTransaction t = saveManyPropRequest.getTransaction(); + Collection additions = null; + Collection deletions = null; + + boolean vanillaCollection = (value instanceof BeanCollection == false); + + if (vanillaCollection || deleteMissingChildren) { + // delete all intersection rows and then treat all + // beans in the collection as additions + deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t); + } + + if (saveManyPropRequest.isInsertedParent() || vanillaCollection || deleteMissingChildren) { + // treat everything in the list/set/map as an intersection addition + if (value instanceof Map) { + additions = ((Map) value).values(); + } else if (value instanceof Collection) { + additions = (Collection) value; + } else { + String msg = "Unhandled ManyToMany type " + value.getClass().getName() + " for " + prop.getFullBeanName(); + throw new PersistenceException(msg); + } + if (!vanillaCollection) { + ((BeanCollection) value).modifyReset(); + } + } else { + // BeanCollection so get the additions/deletions + BeanCollection manyValue = (BeanCollection) value; + additions = manyValue.getModifyAdditions(); + deletions = manyValue.getModifyRemovals(); + // reset so the changes are only processed once + manyValue.modifyReset(); + } + + t.depth(+1); + + if (additions != null && !additions.isEmpty()) { + for (Object otherBean : additions) { + // the object from the 'other' side of the ManyToMany + if (deletions != null && deletions.remove(otherBean)) { + String m = "Inserting and Deleting same object? " + otherBean; + if (t.isLogSummary()) { + t.logInternal(m); + } + logger.log(Level.WARNING, m); + + } else { + if (!prop.hasImportedId(otherBean)) { + String msg = "ManyToMany bean " + otherBean + " does not have an Id value."; + throw new PersistenceException(msg); + + } else { + // build a intersection row for 'insert' + IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherBean); + SqlUpdate sqlInsert = intRow.createInsert(server); + executeSqlUpdate(sqlInsert, t); + } + } + } + } + if (deletions != null && !deletions.isEmpty()) { + for (Object otherDelete : deletions) { + // the object from the 'other' side of the ManyToMany + // build a intersection row for 'delete' + IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete); + SqlUpdate sqlDelete = intRow.createDelete(server); + executeSqlUpdate(sqlDelete, t); + } + } + + // decrease the depth back to what it was + t.depth(-1); + } + + private int deleteAssocManyIntersection(Object bean, BeanPropertyAssocMany many, Transaction t) { + + // delete all intersection rows for this bean + IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean); + SqlUpdate sqlDelete = intRow.createDeleteChildren(server); + + return executeSqlUpdate(sqlDelete, t); + } + + /** + * Delete beans in any associated many. + *

    + * This is called prior to deleting the parent bean. + *

    + */ + private void deleteAssocMany(PersistRequestBean request) { + + SpiTransaction t = request.getTransaction(); + t.depth(-1); + + BeanDescriptor desc = request.getBeanDescriptor(); + Object parentBean = request.getBean(); + + BeanPropertyAssocOne[] expOnes = desc.propertiesOneExportedDelete(); + if (expOnes.length > 0) { + + DeleteUnloadedForeignKeys unloaded = null; + for (int i = 0; i < expOnes.length; i++) { + BeanPropertyAssocOne prop = expOnes[i]; + if (request.isLoadedProperty(prop)) { + Object detailBean = prop.getValue(parentBean); + if (detailBean != null) { + deleteRecurse(detailBean, t); + } + } else { + if (unloaded == null) { + unloaded = new DeleteUnloadedForeignKeys(server, request); + } + unloaded.add(prop); + } + } + if (unloaded != null) { + unloaded.queryForeignKeys(); + unloaded.deleteCascade(); + } + } + + // Many's with delete cascade + BeanPropertyAssocMany[] manys = desc.propertiesManyDelete(); + for (int i = 0; i < manys.length; i++) { + if (manys[i].isManyToMany()) { + // delete associated rows from intersection table + deleteAssocManyIntersection(parentBean, manys[i], t); + + } else { + + if (ModifyListenMode.REMOVALS.equals(manys[i].getModifyListenMode())) { + // PrivateOwned ... + Object details = manys[i].getValueUnderlying(parentBean); + if (details instanceof BeanCollection) { + Set modifyRemovals = ((BeanCollection) details).getModifyRemovals(); + if (modifyRemovals != null && !modifyRemovals.isEmpty()) { + + // delete the orphans that have been removed from the collection + for (Object detailBean : modifyRemovals) { + if (manys[i].hasId(detailBean)) { + deleteRecurse(detailBean, t); + } + } + } + } + } + + deleteManyDetails(t, desc, parentBean, manys[i], null); + } + } + + // restore the depth + t.depth(+1); + } + + /** + * Delete the 'many' detail beans for a given parent bean. + *

    + * For stateless updates this deletes details beans that are no longer in + * the many - the excludeDetailIds holds the detail beans that are in the + * collection (and should not be deleted). + *

    + */ + private void deleteManyDetails(SpiTransaction t, BeanDescriptor desc, Object parentBean, + BeanPropertyAssocMany many, ArrayList excludeDetailIds) { + + if (many.getCascadeInfo().isDelete()) { + // cascade delete the beans in the collection + BeanDescriptor targetDesc = many.getTargetDescriptor(); + if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isUsingL2Cache()) { + // Just delete all the children with one statement + IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds); + SqlUpdate sqlDelete = intRow.createDelete(server); + executeSqlUpdate(sqlDelete, t); + + } else { + // Delete recurse using the Id values of the children + Object parentId = desc.getId(parentBean); + List idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds); + if (!idsByParentId.isEmpty()) { + delete(targetDesc, null, idsByParentId, t); + } + } + } + } + + /** + * Save any associated one beans. + */ + private void saveAssocOne(PersistRequestBean request) { + + BeanDescriptor desc = request.getBeanDescriptor(); + + // imported ones with save cascade + BeanPropertyAssocOne[] ones = desc.propertiesOneImportedSave(); + + for (int i = 0; i < ones.length; i++) { + BeanPropertyAssocOne prop = ones[i]; + + // check for partial objects + if (request.isLoadedProperty(prop)) { + Object detailBean = prop.getValue(request.getBean()); + if (detailBean != null) { + if (isReference(detailBean)) { + // skip saving a reference + } else if (request.isParent(detailBean)) { + // skip saving the parent as already saved + } else if (prop.isSaveRecurseSkippable(detailBean)) { + // we can skip saving this bean + + } else { + SpiTransaction t = request.getTransaction(); + t.depth(-1); + saveRecurse(detailBean, t, null); + t.depth(+1); + } + } + } + } + } + + /** + * Return true if the bean is a reference. + */ + private boolean isReference(Object bean) { + return (bean instanceof EntityBean) && ((EntityBean) bean)._ebean_getIntercept().isReference(); + } + + /** + * Support for loading any Imported Associated One properties that are not + * loaded but required for Delete cascade. + */ + private DeleteUnloadedForeignKeys getDeleteUnloadedForeignKeys(PersistRequestBean request) { + + DeleteUnloadedForeignKeys fkeys = null; + + BeanPropertyAssocOne[] ones = request.getBeanDescriptor().propertiesOneImportedDelete(); + for (int i = 0; i < ones.length; i++) { + if (!request.isLoadedProperty(ones[i])) { + // we have cascade Delete on a partially populated bean and + // this property was not loaded (so we are going to have to fetch it) + if (fkeys == null) { + fkeys = new DeleteUnloadedForeignKeys(server, request); + } + fkeys.add(ones[i]); + } + } + + return fkeys; + } + + /** + * Delete any associated one beans. + */ + private void deleteAssocOne(PersistRequestBean request) { + + BeanDescriptor desc = request.getBeanDescriptor(); + BeanPropertyAssocOne[] ones = desc.propertiesOneImportedDelete(); + + for (int i = 0; i < ones.length; i++) { + BeanPropertyAssocOne prop = ones[i]; + if (!request.isLoadedProperty(prop)) { + // handled by DeleteUnloadedForeignKeys that was built + // via getDeleteUnloadedForeignKeys(); + } else { + Object detailBean = prop.getValue(request.getBean()); + if (detailBean != null && prop.hasId(detailBean)) { + deleteRecurse(detailBean, request.getTransaction()); + } + } + } + } + + /** + * Set Id Generated value for insert. + */ + private void setIdGenValue(PersistRequestBean request) { + + BeanDescriptor desc = request.getBeanDescriptor(); + if (!desc.isUseIdGenerator()) { + return; + } + + BeanProperty idProp = desc.getSingleIdProperty(); + if (idProp == null || idProp.isEmbedded()) { + // not supporting IdGeneration for concatenated or Embedded + return; + } + + Object bean = request.getBean(); + Object uid = idProp.getValue(bean); + + if (DmlUtil.isNullOrZero(uid)) { + + // generate the nextId and set it to the property + Object nextId = desc.nextId(request.getTransaction()); + + // cast the data type if required and set it + desc.convertSetId(nextId, bean); + } + } + + /** + * Return the details of the collection or map taking care to avoid + * unnecessary fetching of the data. + */ + private Collection getDetailsIterator(Object o) { + if (o == null) { + return null; + } + if (o instanceof BeanCollection) { + BeanCollection bc = (BeanCollection) o; + if (!bc.isPopulated()) { + return null; + } + return bc.getActualDetails(); + } + + if (o instanceof Map) { + // yes, we want the entrySet (to set the keys) + return ((Map) o).entrySet(); + + } else if (o instanceof Collection) { + return ((Collection) o); + } + String m = "expecting a Map or Collection but got [" + o.getClass().getName() + "]"; + throw new PersistenceException(m); + } + + /** + * Create the Persist Request Object that wraps all the objects used to + * perform an insert, update or delete. + */ + @SuppressWarnings("unchecked") + private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean) { + BeanManager mgr = getBeanManager(bean); + if (mgr == null) { + throw new PersistenceException(errNotRegistered(bean.getClass())); + } + return (PersistRequestBean) createRequest(bean, t, parentBean, mgr); + } + + private String errNotRegistered(Class beanClass) { + String msg = "The type [" + beanClass + "] is not a registered entity?"; + msg += " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath."; + msg += " If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar()."; + return msg; + } + + /** + * Create the Persist Request Object that wraps all the objects used to + * perform an insert, update or delete. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private PersistRequestBean createRequest(Object bean, Transaction t, Object parentBean, BeanManager mgr) { + + if (mgr.isLdapEntityType()) { + return new LdapPersistBeanRequest(server, bean, parentBean, mgr, ldapPersister); + } + return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute); + } + + /** + * Return the BeanDescriptor for a bean that is being persisted. + *

    + * Note that this checks to see if the bean is a MapBean with a tableName. + * If so it will return the table based BeanDescriptor. + *

    + */ + @SuppressWarnings("unchecked") + private BeanManager getBeanManager(T bean) { + + return (BeanManager) beanDescriptorManager.getBeanManager(bean.getClass()); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java new file mode 100644 index 000000000..864a35eea --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DeleteUnloadedForeignKeys.java @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; + +/** + * Used for deletion of a partially populated bean where some cascade delete + * properties where not loaded. + *

    + * This bean effectively holds the foreign properties that where not loaded, and + * helps fetch the foreign keys and delete the appropriate rows. + *

    + * + * @author rbygrave + */ +public class DeleteUnloadedForeignKeys { + + private final List> propList = new ArrayList>(4); + + private final SpiEbeanServer server; + + private final PersistRequestBean request; + + private Object beanWithForeignKeys; + + public DeleteUnloadedForeignKeys(SpiEbeanServer server, PersistRequestBean request) { + this.server = server; + this.request = request; + } + + public boolean isEmpty() { + return propList.isEmpty(); + } + + public void add(BeanPropertyAssocOne prop) { + propList.add(prop); + } + + /** + * Execute a query fetching the missing (unloaded) foreign keys. We need to + * fetch these key values before the parent bean is deleted. + */ + public void queryForeignKeys() { + + BeanDescriptor descriptor = request.getBeanDescriptor(); + SpiQuery q = (SpiQuery) server.createQuery(descriptor.getBeanType()); + + Object id = request.getBeanId(); + + StringBuilder sb = new StringBuilder(30); + for (int i = 0; i < propList.size(); i++) { + sb.append(propList.get(i).getName()).append(","); + } + + // run query in a separate persistence context + q.setPersistenceContext(new DefaultPersistenceContext()); + q.setAutofetch(false); + q.select(sb.toString()); + q.where().idEq(id); + + SpiTransaction t = request.getTransaction(); + if (t.isLogSummary()) { + t.logInternal("-- Ebean fetching foreign key values for delete of " + descriptor.getName() + " id:" + id); + } + beanWithForeignKeys = server.findUnique(q, t); + } + + /** + * Delete the rows relating to the foreign keys. These deletions occur after + * the parent bean has been deleted. + */ + public void deleteCascade() { + + for (int i = 0; i < propList.size(); i++) { + BeanPropertyAssocOne prop = propList.get(i); + Object detailBean = prop.getValue(beanWithForeignKeys); + + // if bean exists with a unique id then delete it + if (detailBean != null && prop.hasId(detailBean)) { + server.delete(detailBean, request.getTransaction()); + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java new file mode 100644 index 000000000..9d17f9fc1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java @@ -0,0 +1,34 @@ +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebeaninternal.server.deploy.parse.DetectScala; + +/** + * Utility object with helper methods for DML. + */ +public class DmlUtil { + + private static final boolean hasScalaSupport = DetectScala.hasScalaSupport(); + + /** + * Return true if the value is null or a Numeric 0 (for primitive int's and long's) or Option empty. + */ + public static boolean isNullOrZero(Object value){ + if (value == null){ + return true; + } + + if (value instanceof Number){ + return ((Number)value).longValue() == 0l; + } + + if (hasScalaSupport){ + if (value instanceof scala.Option) { + if (((scala.Option) value).isEmpty()) { + return true; + } + } + } + + return false; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java new file mode 100644 index 000000000..8a84b8073 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiCallableSql; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; + +/** + * Handles the execution of CallableSql requests. + */ +public class ExeCallableSql { + + private static final Logger logger = Logger.getLogger(ExeCallableSql.class.getName()); + + private final Binder binder; + + private final PstmtFactory pstmtFactory; + + public ExeCallableSql(Binder binder, PstmtBatch pstmtBatch) { + this.binder = binder; + // no batch support for CallableStatement in Oracle anyway + this.pstmtFactory = new PstmtFactory(null); + } + + /** + * execute the CallableSql requests. + */ + public int execute(PersistRequestCallableSql request) { + + SpiTransaction t = request.getTransaction(); + + boolean batchThisRequest = t.isBatchThisRequest(); + + CallableStatement cstmt = null; + try { + + cstmt = bindStmt(request, batchThisRequest); + + if (batchThisRequest){ + cstmt.addBatch(); + // return -1 to indicate batch mode + return -1; + + } else { + // handles executeOverride() and also + // reading of registered OUT parameters + int rowCount = request.executeUpdate(); + request.postExecute(); + return rowCount; + + } + + } catch (SQLException ex) { + throw new PersistenceException(ex); + + } finally { + if (!batchThisRequest && cstmt != null) { + try { + cstmt.close(); + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + } + } + + + private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException { + + SpiCallableSql callableSql = request.getCallableSql(); + SpiTransaction t = request.getTransaction(); + + String sql = callableSql.getSql(); + + BindParams bindParams = callableSql.getBindParams(); + + // process named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + + boolean logSql = request.isLogSql(); + + CallableStatement cstmt; + if (batchThisRequest){ + cstmt = pstmtFactory.getCstmt(t, logSql, sql, request); + + } else { + if (logSql){ + t.logInternal(sql); + } + cstmt = pstmtFactory.getCstmt(t, sql); + } + + if (callableSql.getTimeout() > 0){ + cstmt.setQueryTimeout(callableSql.getTimeout()); + } + + String bindLog = null; + if (!bindParams.isEmpty()){ + bindLog = binder.bind(bindParams, new DataBind(cstmt)); + } + + request.setBindLog(bindLog); + + // required to read OUT params later + request.setBound(bindParams, cstmt); + + return cstmt; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java new file mode 100644 index 000000000..e1da81951 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; + +/** + * Executes the UpdateSql requests. + */ +public class ExeOrmUpdate { + + private static final Logger logger = Logger.getLogger(ExeOrmUpdate.class.getName()); + + private final Binder binder; + + private final PstmtFactory pstmtFactory; + + /** + * Create with a given binder. + */ + public ExeOrmUpdate(Binder binder, PstmtBatch pstmtBatch) { + this.pstmtFactory = new PstmtFactory(pstmtBatch); + this.binder = binder; + } + + /** + * Execute the orm update request. + */ + public int execute(PersistRequestOrmUpdate request) { + + SpiTransaction t = request.getTransaction(); + + boolean batchThisRequest = t.isBatchThisRequest(); + + PreparedStatement pstmt = null; + try { + + pstmt = bindStmt(request, batchThisRequest); + + if (batchThisRequest){ + PstmtBatch pstmtBatch = request.getPstmtBatch(); + if (pstmtBatch != null){ + pstmtBatch.addBatch(pstmt); + } else { + pstmt.addBatch(); + } + // return -1 to indicate batch mode + return -1; + + } else { + SpiUpdate ormUpdate = request.getOrmUpdate(); + if (ormUpdate.getTimeout() > 0){ + pstmt.setQueryTimeout(ormUpdate.getTimeout()); + } + + int rowCount = pstmt.executeUpdate(); + request.checkRowCount(rowCount); + request.postExecute(); + return rowCount; + + } + + } catch (SQLException ex) { + SpiUpdate ormUpdate = request.getOrmUpdate(); + String msg = "Error executing: "+ormUpdate.getGeneratedSql(); + throw new PersistenceException(msg, ex); + + } finally { + if (!batchThisRequest && pstmt != null) { + try { + pstmt.close(); + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + } + } + + /** + * Convert bean and property names to db table and columns. + */ + private String translate(PersistRequestOrmUpdate request, String sql) { + + BeanDescriptor descriptor = request.getBeanDescriptor(); + return descriptor.convertOrmUpdateToSql(sql); + } + + private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException { + + SpiUpdate ormUpdate = request.getOrmUpdate(); + SpiTransaction t = request.getTransaction(); + + String sql = ormUpdate.getUpdateStatement(); + + // convert bean and property names to table and + // column names if required + sql = translate(request, sql); + + BindParams bindParams = ormUpdate.getBindParams(); + + // process named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + + ormUpdate.setGeneratedSql(sql); + + boolean logSql = request.isLogSql(); + + PreparedStatement pstmt; + if (batchThisRequest){ + pstmt = pstmtFactory.getPstmt(t, logSql, sql, request); + + } else { + if (logSql){ + t.logInternal(sql); + } + pstmt = pstmtFactory.getPstmt(t, sql); + } + + String bindLog = null; + if (!bindParams.isEmpty()){ + bindLog = binder.bind(bindParams, new DataBind(pstmt)); + } + + request.setBindLog(bindLog); + + return pstmt; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java new file mode 100644 index 000000000..25aea1350 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java @@ -0,0 +1,218 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiSqlUpdate; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql.SqlType; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; + +/** + * Executes the UpdateSql requests. + */ +public class ExeUpdateSql { + + private static final Logger logger = Logger.getLogger(ExeUpdateSql.class.getName()); + + private final Binder binder; + + private final PstmtFactory pstmtFactory; + + private final PstmtBatch pstmtBatch; + + //TODO: get defaultBatchSize + private int defaultBatchSize = 20; + + /** + * Create with a given binder. + */ + public ExeUpdateSql(Binder binder, PstmtBatch pstmtBatch) { + this.binder = binder; + this.pstmtBatch = pstmtBatch; + this.pstmtFactory = new PstmtFactory(pstmtBatch); + } + + /** + * Execute the UpdateSql request. + */ + public int execute(PersistRequestUpdateSql request) { + + SpiTransaction t = request.getTransaction(); + + boolean batchThisRequest = t.isBatchThisRequest(); + + PreparedStatement pstmt = null; + try { + + pstmt = bindStmt(request, batchThisRequest); + + if (batchThisRequest){ + if (pstmtBatch != null){ + pstmtBatch.addBatch(pstmt); + } else { + pstmt.addBatch(); + } + // return -1 to indicate batch mode + return -1; + + } else { + int rowCount = pstmt.executeUpdate(); + request.checkRowCount(rowCount); + request.postExecute(); + return rowCount; + + } + + } catch (SQLException ex) { + throw new PersistenceException(ex); + + } finally { + if (!batchThisRequest && pstmt != null) { + try { + pstmt.close(); + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + } + } + + + private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException { + + SpiSqlUpdate updateSql = request.getUpdateSql(); + SpiTransaction t = request.getTransaction(); + + String sql = updateSql.getSql(); + + BindParams bindParams = updateSql.getBindParams(); + + // process named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + + boolean logSql = request.isLogSql(); + + PreparedStatement pstmt; + if (batchThisRequest){ + pstmt = pstmtFactory.getPstmt(t, logSql, sql, request); + if (pstmtBatch != null){ + // oracle specific JDBC setting batch size ahead of time + int batchSize = t.getBatchSize(); + if (batchSize < 1){ + batchSize = defaultBatchSize; + } + pstmtBatch.setBatchSize(pstmt, batchSize); + } + + } else { + if (logSql){ + t.logInternal(sql); + } + pstmt = pstmtFactory.getPstmt(t, sql); + } + + if (updateSql.getTimeout() > 0){ + pstmt.setQueryTimeout(updateSql.getTimeout()); + } + + String bindLog = null; + if (!bindParams.isEmpty()){ + bindLog = binder.bind(bindParams, new DataBind(pstmt)); + } + + request.setBindLog(bindLog); + + // derive the statement type (for TransactionEvent) + parseUpdate(sql, request); + + return pstmt; + } + + + private void determineType(String word1, String word2, String word3, PersistRequestUpdateSql request) { + if (word1.equalsIgnoreCase("UPDATE")) { + request.setType(SqlType.SQL_UPDATE, word2, "UpdateSql"); + + } else if (word1.equalsIgnoreCase("DELETE")) { + request.setType(SqlType.SQL_DELETE, word3, "DeleteSql"); + + } else if (word1.equalsIgnoreCase("INSERT")) { + request.setType(SqlType.SQL_INSERT, word3, "InsertSql"); + + } else { + request.setType(SqlType.SQL_UNKNOWN, null, "UnknownSql"); + + } + } + + private void parseUpdate(String sql, PersistRequestUpdateSql request) { + + int start = ltrim(sql); + + int[] pos = new int[3]; + int spaceCount = 0; + + int len = sql.length(); + for (int i = start; i < len; i++) { + char c = sql.charAt(i); + if (Character.isWhitespace(c)) { + pos[spaceCount] = i; + spaceCount++; + if (spaceCount > 2){ + break; + } + } + } + + String firstWord = sql.substring(0, pos[0]); + String secWord = sql.substring(pos[0]+1, pos[1]); + String thirdWord; + if (pos[2] == 0){ + // there is nothing after the table name + thirdWord = sql.substring(pos[1]+1); + } else { + thirdWord = sql.substring(pos[1]+1, pos[2]); + } + + determineType(firstWord, secWord, thirdWord, request); + } + + private int ltrim(String s) { + int len = s.length(); + int i = 0; + for (i = 0; i < len; i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + return 0; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java new file mode 100644 index 000000000..20b81b074 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; + +/** + * The actual execution of persist requests. + *

    + * A Persister 'front-ends' this object and handles the + * batching, cascading, concurrency mode detection etc. + *

    + * + */ +public interface PersistExecute { + + /** + * Create a BatchControl for the current transaction. + */ + public BatchControl createBatchControl(SpiTransaction t); + + /** + * Execute a Bean (or MapBean) insert. + */ + public void executeInsertBean(PersistRequestBean request); + + /** + * Execute a Bean (or MapBean) update. + */ + public void executeUpdateBean(PersistRequestBean request); + + /** + * Execute a Bean (or MapBean) delete. + */ + public void executeDeleteBean(PersistRequestBean request); + + /** + * Execute a Update. + */ + public int executeOrmUpdate(PersistRequestOrmUpdate request); + + /** + * Execute a CallableSql. + */ + public int executeSqlCallable(PersistRequestCallableSql request); + + /** + * Execute a UpdateSql. + */ + public int executeSqlUpdate(PersistRequestUpdateSql request); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java new file mode 100644 index 000000000..07ca4b2be --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PstmtBatch; + +/** + * Factory for creating Statements. + *

    + * This is only used by CallableSql and UpdateSql requests and does not support + * getGeneratedKeys. + *

    + */ +public class PstmtFactory { + + + private final PstmtBatch pstmtBatch; + + public PstmtFactory(PstmtBatch pstmtBatch) { + this.pstmtBatch = pstmtBatch; + } + + /** + * Get a callable statement without any batching. + */ + public CallableStatement getCstmt(SpiTransaction t, String sql) throws SQLException { + Connection conn = t.getInternalConnection(); + return conn.prepareCall(sql); + } + + /** + * Get a prepared statement without any batching. + */ + public PreparedStatement getPstmt(SpiTransaction t, String sql) throws SQLException { + Connection conn = t.getInternalConnection(); + return conn.prepareStatement(sql); + } + + /** + * Return a prepared statement taking into account batch requirements. + */ + public PreparedStatement getPstmt(SpiTransaction t, boolean logSql, String sql, BatchPostExecute batchExe) + throws SQLException { + + BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); + PreparedStatement stmt = batch.getStmt(sql, batchExe); + + if (stmt != null) { + return stmt; + } + + if (logSql){ + t.logInternal(sql); + } + + Connection conn = t.getInternalConnection(); + stmt = conn.prepareStatement(sql); + + if (pstmtBatch != null){ + pstmtBatch.setBatchSize(stmt, t.getBatchControl().getBatchSize()); + } + + BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, pstmtBatch, false); + batch.addStmt(bs, batchExe); + return stmt; + } + + /** + * Return a callable statement taking into account batch requirements. + */ + public CallableStatement getCstmt(SpiTransaction t, boolean logSql, String sql, BatchPostExecute batchExe) + throws SQLException { + + BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); + CallableStatement stmt = (CallableStatement) batch.getStmt(sql, batchExe); + + if (stmt != null) { + return stmt; + } + + if (logSql){ + t.logInternal(sql); + } + + Connection conn = t.getInternalConnection(); + stmt = conn.prepareCall(sql); + + BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, pstmtBatch, false); + batch.addStmt(bs, batchExe); + return stmt; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java new file mode 100644 index 000000000..29ffbf6bd --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import javax.persistence.OptimisticLockException; + +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Delete bean handler. + */ +public class DeleteHandler extends DmlHandler { + + private final DeleteMeta meta; + + public DeleteHandler(PersistRequestBean persist, DeleteMeta meta) { + super(persist, meta.isEmptyStringAsNull()); + this.meta = meta; + } + + /** + * Generate and bind the delete statement. + */ + public void bind() throws SQLException { + + sql = meta.getSql(persistRequest); + + SpiTransaction t = persistRequest.getTransaction(); + boolean isBatch = t.isBatchThisRequest(); + + PreparedStatement pstmt; + if (isBatch) { + pstmt = getPstmt(t, sql, persistRequest, false); + + } else { + logSql(sql); + pstmt = getPstmt(t, sql, false); + } + dataBind = new DataBind(pstmt); + + bindLogAppend("Binding Delete ["); + bindLogAppend(meta.getTableName()); + bindLogAppend("] where["); + + meta.bind(persistRequest, this); + + bindLogAppend("]"); + + // log the binding to transaction log if requested + logBinding(); + } + + /** + * Execute the delete non-batch. + */ + public void execute() throws SQLException, OptimisticLockException { + int rowCount = dataBind.executeUpdate(); + checkRowCount(rowCount); + } + + @Override + public boolean isIncluded(BeanProperty prop) { + return prop.isDbUpdatable() && super.isIncluded(prop); + } + + @Override + public boolean isIncludedWhere(BeanProperty prop) { + return prop.isDbUpdatable() && (loadedProps == null || loadedProps.contains(prop.getName())); + } + + public void registerDerivedRelationship(DerivedRelationshipData assocBean) { + throw new RuntimeException("Never called on delete"); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java new file mode 100644 index 000000000..a2c312aa9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; +import java.util.Set; + +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; + +/** + * Meta data for delete handler. The meta data is for a particular bean type. It + * is considered immutable and is thread safe. + */ +public final class DeleteMeta { + + private final String sqlVersion; + + private final String sqlNone; + + private final BindableId id; + + private final Bindable version; + + private final Bindable all; + + private final String tableName; + + private final boolean emptyStringAsNull; + + public DeleteMeta(boolean emptyStringAsNull, BeanDescriptor desc, BindableId id, Bindable version, Bindable all) { + this.emptyStringAsNull = emptyStringAsNull; + this.tableName = desc.getBaseTable(); + this.id = id; + this.version = version; + this.all = all; + + sqlNone = genSql(ConcurrencyMode.NONE); + sqlVersion = genSql(ConcurrencyMode.VERSION); + } + + public boolean isEmptyStringAsNull() { + return emptyStringAsNull; + } + + /** + * Return the table name. + */ + public String getTableName() { + return tableName; + } + + /** + * Bind the request based on the concurrency mode. + */ + public void bind(PersistRequestBean persist, DmlHandler bind) throws SQLException { + + Object bean = persist.getBean(); + + id.dmlBind(bind, false, bean); + + switch (persist.getConcurrencyMode()) { + case VERSION: + version.dmlBind(bind, false, bean); + break; + + case ALL: + Object oldBean = persist.getOldValues(); + all.dmlBindWhere(bind, true, oldBean); + break; + + default: + break; + } + } + + /** + * get or generate the sql based on the concurrency mode. + */ + public String getSql(PersistRequestBean request) throws SQLException { + + if (id.isEmpty()) { + throw new IllegalStateException("Can not deleteById on " + request.getFullName() + " as no @Id property"); + } + + switch (request.determineConcurrencyMode()) { + case NONE: + return sqlNone; + + case VERSION: + return sqlVersion; + + case ALL: + return genDynamicWhere(request.getLoadedProperties(), request.getOldValues()); + + default: + throw new RuntimeException("Invalid mode " + request.determineConcurrencyMode()); + } + } + + private String genSql(ConcurrencyMode conMode) { + + // delete ... where bcol=? and bc1=? and bc2 is null and ... + + GenerateDmlRequest request = new GenerateDmlRequest(emptyStringAsNull); + + request.append("delete from ").append(tableName); + request.append(" where "); + + request.setWhereIdMode(); + id.dmlAppend(request, false); + + if (ConcurrencyMode.VERSION.equals(conMode)) { + if (version == null) { + return null; + } + version.dmlAppend(request, false); + + } else if (ConcurrencyMode.ALL.equals(conMode)) { + throw new RuntimeException("Never called for ConcurrencyMode.ALL"); + } + + return request.toString(); + } + + /** + * Generate the sql dynamically for where using IS NULL for binding null + * values. + */ + private String genDynamicWhere(Set includedProps, Object oldBean) throws SQLException { + + // always has a preceding id property(s) so the first + // option is always ' and ' and not blank. + + GenerateDmlRequest request = new GenerateDmlRequest(emptyStringAsNull, includedProps, oldBean); + + request.append(sqlNone); + + request.setWhereMode(); + all.dmlWhere(request, true, oldBean); + + return request.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java new file mode 100644 index 000000000..7152a87e0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.persist.BeanPersister; + +/** + * Bean persister that uses the Handler and Meta objects. + *

    + * The design of this is based on the immutable Meta objects. They hold a + * information in the form of lists of Bindable objects. This effectively + * flattens the structure of the bean with embedded and associated objects into + * a flat list of Bindable objects. + *

    + */ +public final class DmlBeanPersister implements BeanPersister { + + private static final Logger logger = Logger.getLogger(DmlBeanPersister.class.getName()); + + private final UpdateMeta updateMeta; + + private final InsertMeta insertMeta; + + private final DeleteMeta deleteMeta; + + + public DmlBeanPersister(UpdateMeta updateMeta, InsertMeta insertMeta, DeleteMeta deleteMeta) { + + this.updateMeta = updateMeta; + this.insertMeta = insertMeta; + this.deleteMeta = deleteMeta; + } + + /** + * execute the bean delete request. + */ + public void delete(PersistRequestBean request) { + + DeleteHandler delete = new DeleteHandler(request, deleteMeta); + execute(request, delete); + } + + /** + * execute the bean insert request. + */ + public void insert(PersistRequestBean request) { + + InsertHandler insert = new InsertHandler(request, insertMeta); + execute(request, insert); + } + + /** + * execute the bean update request. + */ + public void update(PersistRequestBean request) { + + UpdateHandler update = new UpdateHandler(request, updateMeta); + execute(request, update); + } + + /** + * execute request taking batching into account. + */ + private void execute(PersistRequest request, PersistHandler handler) { + + SpiTransaction trans = request.getTransaction(); + boolean batchThisRequest = trans.isBatchThisRequest(); + + try { + + handler.bind(); + + if (batchThisRequest) { + handler.addBatch(); + + } else { + // immediate insert + handler.execute(); + } + + } catch (SQLException e) { + // log the error to the transaction log + String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r","\n"}, "\\n "); + String msg = "ERROR executing DML bindLog["+handler.getBindLog()+"] error["+errMsg+"]"; + if (request.getTransaction().isLogSummary()) { + request.getTransaction().logInternal(msg); + } + + throw new PersistenceException(msg, e); + + } finally { + if (!batchThisRequest && handler != null) { + try { + handler.close(); + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java new file mode 100644 index 000000000..ff5627b83 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.persist.BeanPersister; +import com.avaje.ebeaninternal.server.persist.BeanPersisterFactory; + +/** + * Factory for creating a DmlBeanPersister for a bean type. + */ +public class DmlBeanPersisterFactory implements BeanPersisterFactory { + + private final MetaFactory metaFactory; + + public DmlBeanPersisterFactory(DatabasePlatform dbPlatform) { + this.metaFactory = new MetaFactory(dbPlatform); + } + + + /** + * Create a DmlBeanPersister for the given bean type. + */ + public BeanPersister create(BeanDescriptor desc) { + + UpdateMeta updMeta = metaFactory.createUpdate(desc); + DeleteMeta delMeta = metaFactory.createDelete(desc); + InsertMeta insMeta = metaFactory.createInsert(desc); + + return new DmlBeanPersister(updMeta, insMeta, delMeta); + + } + +} 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 new file mode 100644 index 000000000..44249e872 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java @@ -0,0 +1,418 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.OptimisticLockException; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.BatchedPstmt; +import com.avaje.ebeaninternal.server.persist.BatchedPstmtHolder; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest; +import com.avaje.ebeaninternal.server.type.DataBind; + + +/** + * Base class for Handler implementations. + */ +public abstract class DmlHandler implements PersistHandler, BindableRequest { + + protected static final Logger logger = Logger.getLogger(DmlHandler.class.getName()); + + /** + * The originating request. + */ + protected final PersistRequestBean persistRequest; + + protected final StringBuilder bindLog; + + protected final Set loadedProps; + + protected final SpiTransaction transaction; + + protected final boolean emptyStringToNull; + + protected final boolean logLevelSql; + + /** + * The PreparedStatement used for the dml. + */ + protected DataBind dataBind; + + protected String sql; + + protected ArrayList updateGenValues; + + private Set additionalProps; + +// private boolean checkDelta; +// +// private BeanDelta deltaBean; + + protected DmlHandler(PersistRequestBean persistRequest, boolean emptyStringToNull) { + this.persistRequest = persistRequest; + this.emptyStringToNull = emptyStringToNull; + this.loadedProps = persistRequest.getLoadedProperties(); + this.transaction = persistRequest.getTransaction(); + this.logLevelSql = transaction.isLogSql(); + if (logLevelSql) { + this.bindLog = new StringBuilder(); + } else { + this.bindLog = null; + } + } + +// protected void setCheckDelta(boolean checkDelta) { +// this.checkDelta = checkDelta; +// } + + public PersistRequestBean getPersistRequest() { + return persistRequest; + } + + /** + * Get the sql and bind the statement. + */ + public abstract void bind() throws SQLException; + + /** + * Execute now for non-batch execution. + */ + public abstract void execute() throws SQLException; + + /** + * Check the rowCount. + */ + protected void checkRowCount(int rowCount) throws SQLException, OptimisticLockException { + try { + persistRequest.checkRowCount(rowCount); + persistRequest.postExecute(); + } catch (OptimisticLockException e){ + // add the SQL and bind values to error message + String m = e.getMessage()+" sql["+sql+"] bind["+bindLog+"]"; + persistRequest.getTransaction().log("OptimisticLockException:"+m); + throw new OptimisticLockException(m, null, e.getEntity()); + } + } + + /** + * Add this for batch execution. + */ + public void addBatch() throws SQLException { + PstmtBatch pstmtBatch = persistRequest.getPstmtBatch(); + if (pstmtBatch != null){ + pstmtBatch.addBatch(dataBind.getPstmt()); + } else { + dataBind.getPstmt().addBatch(); + } + } + + /** + * Close the underlying statement. + */ + public void close() { + try { + if (dataBind != null){ + dataBind.close(); + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + /** + * Return the bind log. + */ + public String getBindLog() { + return bindLog == null ? "" : bindLog.toString(); + } + + /** + * Set the Id value that was bound. This value is used for logging summary + * level information. + */ + public void setIdValue(Object idValue) { + persistRequest.setBoundId(idValue); + } + + /** + * Log the bind information to the transaction log. + */ + protected void logBinding() { + if (logLevelSql) { + transaction.logInternal(bindLog.toString()); + } + } + + /** + * Log the sql to the transaction log. + */ + protected void logSql(String sql) { + if (logLevelSql) { + transaction.logInternal(sql); + } + } + + + public boolean isIncluded(BeanProperty prop) { + return (loadedProps == null || loadedProps.contains(prop.getName())); + } + + public boolean isIncludedWhere(BeanProperty prop) { + if (prop.isDbEncrypted()){ + // update without a version property ... + // for encrypted properties only include if it was + // also an updated/modified property + return isIncluded(prop); + } + return prop.isDbUpdatable() && (loadedProps == null || loadedProps.contains(prop.getName())); + } + + /** + * Bind a raw value. Used to bind the discriminator column. + */ + public Object bind(String propName, Object value, int sqlType) throws SQLException { + if (logLevelSql) { + bindLog.append(propName).append("="); + bindLog.append(value).append(", "); + } + dataBind.setObject(value, sqlType); + return value; + } + + public Object bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException { + if (logLevelSql) { + bindLog.append(logPlaceHolder).append(" "); + } + dataBind.setObject(value, sqlType); + return value; + } + + /** + * Bind the value to the preparedStatement. + */ + public Object bind(Object value, BeanProperty prop, String propName, boolean bindNull) throws SQLException { + return bindInternal(logLevelSql, value, prop, propName, bindNull); + } + + /** + * Bind the value to the preparedStatement without logging. + */ + public Object bindNoLog(Object value, BeanProperty prop, String propName, boolean bindNull) throws SQLException { + return bindInternal(false, value, prop, propName, bindNull); + } + + private Object bindInternal(boolean log, Object value, BeanProperty prop, String propName, boolean bindNull) throws SQLException { + + if (!bindNull){ + if (emptyStringToNull && (value instanceof String) && ((String)value).length() == 0){ + // support Oracle conversion of empty string to null + //value = prop.getDbNullValue(value); + value = null; + } + } + + if (!bindNull && value == null) { + // where will have IS NULL clause so don't actually bind + if (log) { + bindLog.append(propName).append("="); + bindLog.append("null, "); + } + } else { + if (log) { + bindLog.append(propName).append("="); + if (prop.isLob()){ + bindLog.append("[LOB]"); + } else { + String sv = String.valueOf(value); + if (sv.length() > 50){ + sv = sv.substring(0,47)+"..."; + } + bindLog.append(sv); + } + bindLog.append(", "); + } + // do the actual binding to PreparedStatement + prop.bind(dataBind, value); +// if (checkDelta) { +// if (!prop.isId() && prop.isDeltaRequired()){ +// if (deltaBean == null){ +// deltaBean = persistRequest.createDeltaBean(); +// transaction.getEvent().addBeanDelta(deltaBean); +// } +// deltaBean.add(prop, value); +// } +// } + } + return value; + } + + /** + * Add the comment to the bind information log. + */ + protected void bindLogAppend(String comment) { + if (logLevelSql) { + bindLog.append(comment); + } + } + + /** + * For generated properties set on insert register as additional + * loaded properties if required. + */ + public final void registerAdditionalProperty(String propertyName) { + if (loadedProps != null && !loadedProps.contains(propertyName)){ + if (additionalProps == null){ + additionalProps = new HashSet(); + } + additionalProps.add(propertyName); + } + } + + /** + * Set any additional (generated) properties to the set of loaded properties + * if required. + */ + protected void setAdditionalProperties() { + if (additionalProps != null){ + // additional generated properties set on insert + // added to the set of loaded properties + additionalProps.addAll(loadedProps); + persistRequest.setLoadedProps(additionalProps); + } + } + + /** + * Register a generated value on a update. This can not be set to the bean + * until after the where clause has been bound for concurrency checking. + *

    + * GeneratedProperty values are likely going to be used for optimistic + * concurrency checking. This includes 'counter' and 'update timestamp' + * generation. + *

    + */ + public void registerUpdateGenValue(BeanProperty prop, Object bean, Object value) { + if (updateGenValues == null) { + updateGenValues = new ArrayList(); + } + updateGenValues.add(new UpdateGenValue(prop, bean, value)); + registerAdditionalProperty(prop.getName()); + } + + + + /** + * Set any update generated values to the bean. Must be called after where + * 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(); + } + } + } + + + /** + * Check with useGeneratedKeys to get appropriate PreparedStatement. + */ + protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean genKeys) throws SQLException { + Connection conn = t.getInternalConnection(); + if (genKeys) { + // the Id generated is always the first column + // Required to stop Oracle10 giving us Oracle rowId?? + // Other jdbc drivers seem fine without this hint. + int[] columns = {1}; + return conn.prepareStatement(sql, columns); + + } else { + return conn.prepareStatement(sql); + } + } + + /** + * Return a prepared statement taking into account batch requirements. + */ + protected PreparedStatement getPstmt(SpiTransaction t, String sql, PersistRequestBean request, boolean genKeys) + throws SQLException { + + BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); + PreparedStatement stmt = batch.getStmt(sql, request); + + if (stmt != null) { + return stmt; + } + + if (logLevelSql){ + t.logInternal(sql); + } + + stmt = getPstmt(t, sql, genKeys); + + PstmtBatch pstmtBatch = request.getPstmtBatch(); + if (pstmtBatch != null){ + pstmtBatch.setBatchSize(stmt, t.getBatchControl().getBatchSize()); + } + + BatchedPstmt bs = new BatchedPstmt(stmt, genKeys, sql, request.getPstmtBatch(), true); + batch.addStmt(bs, request); + 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 Object bean; + + private final Object value; + + private UpdateGenValue(BeanProperty property, Object 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/dml/DmlMode.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlMode.java new file mode 100644 index 000000000..dbf539470 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlMode.java @@ -0,0 +1,22 @@ +package com.avaje.ebeaninternal.server.persist.dml; + +/** + * Used to indicate the part of DML being processed. + */ +public enum DmlMode { + + /** + * The Insert SET. + */ + INSERT, + + /** + * The Update SET. + */ + UPDATE, + + /** + * The Update or Delete WHERE. + */ + WHERE +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GenerateDmlRequest.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GenerateDmlRequest.java new file mode 100644 index 000000000..9368bfa3e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GenerateDmlRequest.java @@ -0,0 +1,152 @@ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.util.Set; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Helper to support the generation of DML statements. + */ +public class GenerateDmlRequest { + + private static final String IS_NULL = " is null"; + + private final boolean emptyStringAsNull; + + private final StringBuilder sb = new StringBuilder(100); + + private final Set includeProps; + private final Set includeWhereProps; + + private final Object oldValues; + + private StringBuilder insertBindBuffer; + + private String prefix; + private String prefix2; + + private int insertMode; + + private int bindColumnCount; + + /** + * Create with includeWhereProps same as includeProps. + */ + public GenerateDmlRequest(boolean emptyStringAsNull, Set includeProps, Object oldValues) { + this(emptyStringAsNull, includeProps, includeProps, oldValues); + } + + /** + * Create from a PersistRequestBean. + */ + public GenerateDmlRequest(boolean emptyStringAsNull, Set includeProps, Set includeWhereProps, Object oldValues) { + this.emptyStringAsNull = emptyStringAsNull; + this.includeProps = includeProps; + this.includeWhereProps = includeWhereProps; + this.oldValues = oldValues; + } + + /** + * Create for generating standard all properties DML/SQL. + */ + public GenerateDmlRequest(boolean emptyStringAsNull) { + this(emptyStringAsNull, null, null, null); + } + + public GenerateDmlRequest append(String s) { + sb.append(s); + return this; + } + + public boolean isDbNull(Object v) { + return v == null || (emptyStringAsNull && (v instanceof String) && ((String) v).length() == 0); + } + + /** + * Return true if this property should be included in the set clause. + */ + public boolean isIncluded(BeanProperty prop) { + return (includeProps == null || includeProps.contains(prop.getName())); + } + + /** + * Return true if this property should be included in the where clause. + */ + public boolean isIncludedWhere(BeanProperty prop) { + return (includeWhereProps == null || includeWhereProps.contains(prop.getName())); + } + + public void appendColumnIsNull(String column) { + appendColumn(column, IS_NULL); + } + + public void appendColumn(String column) { + String bind = (insertMode > 0) ? "?" : "=?"; + appendColumn(column, bind); + } + + public void appendColumn(String column, String suffik) { + appendColumn(column, "", suffik); + } + + public void appendColumn(String column, String expr, String suffik) { + + ++bindColumnCount; + + sb.append(prefix); + sb.append(column); + sb.append(expr); + if (insertMode > 0) { + if (insertMode++ > 1) { + insertBindBuffer.append(","); + } + insertBindBuffer.append(suffik); + } else { + sb.append(suffik); + } + + if (prefix2 != null) { + prefix = prefix2; + prefix2 = null; + } + } + + public int getBindColumnCount() { + return bindColumnCount; + } + + public String getInsertBindBuffer() { + return insertBindBuffer.toString(); + } + + public String toString() { + return sb.toString(); + } + + public void setWhereMode() { + this.prefix = " and "; + this.prefix2 = " and "; + } + + public void setWhereIdMode() { + this.prefix = ""; + this.prefix2 = " and "; + } + + public void setInsertSetMode() { + this.insertBindBuffer = new StringBuilder(100); + this.insertMode = 1; + this.prefix = ""; + this.prefix2 = ", "; + } + + public void setUpdateSetMode() { + this.prefix = ""; + this.prefix2 = ", "; + } + + public Object getOldValues() { + return oldValues; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java new file mode 100644 index 000000000..e80fd9769 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java @@ -0,0 +1,258 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.List; +import java.util.logging.Level; + +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.Message; +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.persist.DmlUtil; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Insert bean handler. + */ +public class InsertHandler extends DmlHandler { + + /** + * The associated InsertMeta data. + */ + private final InsertMeta meta; + + /** + * Set to true when the key is concatenated. + */ + private final boolean concatinatedKey; + + /** + * Flag set when using getGeneratedKeys. + */ + private boolean useGeneratedKeys; + + /** + * A SQL Select used to fetch back the Id where generatedKeys is not + * supported. + */ + private String selectLastInsertedId; + + /** + * Create to handle the insert execution. + */ + public InsertHandler(PersistRequestBean persist, InsertMeta meta) { + super(persist, meta.isEmptyStringToNull()); + this.meta = meta; + this.concatinatedKey = meta.isConcatinatedKey(); + } + + @Override + public boolean isIncluded(BeanProperty prop) { + return prop.isDbInsertable() && (super.isIncluded(prop)); + } + + /** + * Generate and bind the insert statement. + */ + public void bind() throws SQLException { + + BeanDescriptor desc = persistRequest.getBeanDescriptor(); + Object bean = persistRequest.getBean(); + + Object idValue = desc.getId(bean); + + boolean withId = !DmlUtil.isNullOrZero(idValue); + + // check to see if we are going to use generated keys + if (!withId) { + if (concatinatedKey) { + // expecting a concatenated key that can + // be built from supplied AssocOne beans + withId = meta.deriveConcatenatedId(persistRequest); + + } else if (meta.supportsGetGeneratedKeys()) { + // Identity with getGeneratedKeys + useGeneratedKeys = true; + } else { + // use a query to get the last inserted id + selectLastInsertedId = meta.getSelectLastInsertedId(); + } + } + + SpiTransaction t = persistRequest.getTransaction(); + boolean isBatch = t.isBatchThisRequest(); + + // get the appropriate sql + sql = meta.getSql(withId); + + PreparedStatement pstmt; + if (isBatch) { + pstmt = getPstmt(t, sql, persistRequest, useGeneratedKeys); + + } else { + logSql(sql); + pstmt = getPstmt(t, sql, useGeneratedKeys); + } + dataBind = new DataBind(pstmt); + + bindLogAppend("Binding Insert ["); + bindLogAppend(desc.getBaseTable()); + bindLogAppend("] set["); + + // bind the bean property values + meta.bind(this, bean, withId); + + bindLogAppend("]"); + logBinding(); + } + + /** + * Check with useGeneratedKeys to get appropriate PreparedStatement. + */ + @Override + protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean useGeneratedKeys) throws SQLException { + Connection conn = t.getInternalConnection(); + if (useGeneratedKeys) { + return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + + } else { + return conn.prepareStatement(sql); + } + } + + /** + * Execute the insert in a normal non batch fashion. Additionally using + * getGeneratedKeys if required. + */ + public void execute() throws SQLException, OptimisticLockException { + int rc = dataBind.executeUpdate(); + if (useGeneratedKeys) { + // get the auto-increment value back and set into the bean + getGeneratedKeys(); + + } else if (selectLastInsertedId != null) { + // fetch back the Id using a query + fetchGeneratedKeyUsingSelect(); + } + + checkRowCount(rc); + setAdditionalProperties(); + executeDerivedRelationships(); + } + + protected void executeDerivedRelationships() { + List derivedRelationships = persistRequest.getDerivedRelationships(); + if (derivedRelationships != null) { + for (int i = 0; i < derivedRelationships.size(); i++) { + DerivedRelationshipData derivedRelationshipData = derivedRelationships.get(i); + + EbeanServer ebeanServer = persistRequest.getEbeanServer(); + HashSet updateProps = new HashSet(); + updateProps.add(derivedRelationshipData.getLogicalName()); + ebeanServer.update(derivedRelationshipData.getBean(), updateProps, transaction, false, true); + } + } + } + + /** + * For non batch insert with generated keys. + */ + private void getGeneratedKeys() throws SQLException { + + ResultSet rset = dataBind.getPstmt().getGeneratedKeys(); + try { + if (rset.next()) { + Object idValue = rset.getObject(1); + if (idValue != null) { + persistRequest.setGeneratedKey(idValue); + } + + } else { + throw new PersistenceException(Message.msg("persist.autoinc.norows")); + } + } finally { + try { + rset.close(); + } catch (SQLException ex) { + String msg = "Error closing rset for returning generatedKeys?"; + logger.log(Level.WARNING, msg, ex); + } + } + } + + /** + * For non batch insert with DBs that do not support getGeneratedKeys. Use a + * SQL select to fetch back the Id value. + */ + private void fetchGeneratedKeyUsingSelect() throws SQLException { + + Connection conn = transaction.getConnection(); + + PreparedStatement stmt = null; + ResultSet rset = null; + try { + stmt = conn.prepareStatement(selectLastInsertedId); + rset = stmt.executeQuery(); + if (rset.next()) { + Object idValue = rset.getObject(1); + if (idValue != null) { + persistRequest.setGeneratedKey(idValue); + } + } else { + throw new PersistenceException(Message.msg("persist.autoinc.norows")); + } + } finally { + try { + if (rset != null) { + rset.close(); + } + } catch (SQLException ex) { + String msg = "Error closing rset for fetchGeneratedKeyUsingSelect?"; + logger.log(Level.WARNING, msg, ex); + } + try { + if (stmt != null) { + stmt.close(); + } + } catch (SQLException ex) { + String msg = "Error closing stmt for fetchGeneratedKeyUsingSelect?"; + logger.log(Level.WARNING, msg, ex); + } + } + } + + public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { + persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java new file mode 100644 index 000000000..04e2939b4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java @@ -0,0 +1,208 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; +import java.util.Set; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableDiscriminator; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; + +/** + * Meta data for insert handler. The meta data is for a particular bean type. It + * is considered immutable and is thread safe. + */ +public final class InsertMeta { + + private final String sqlNullId; + + private final String sqlWithId; + + private final BindableId id; + + private final Bindable discriminator; + + private final Bindable all; + + private final boolean supportsGetGeneratedKeys; + + private final boolean concatinatedKey; + + private final String tableName; + + /** + * Used for DB that do not support getGeneratedKeys. + */ + private final String selectLastInsertedId; + + private final Bindable shadowFKey; + + private final String[] identityDbColumns; + + private final boolean emptyStringToNull; + + public InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor desc, Bindable shadowFKey, BindableId id, Bindable all) { + + this.emptyStringToNull = dbPlatform.isTreatEmptyStringsAsNull(); + this.tableName = desc.getBaseTable(); + this.discriminator = getDiscriminator(desc); + this.id = id; + this.all = all; + this.shadowFKey = shadowFKey; + + this.sqlWithId = genSql(false, null); + + // only available for single Id property + if (id.isConcatenated()) { + // concatenated key + this.concatinatedKey = true; + this.identityDbColumns = null; + this.sqlNullId = null; + this.supportsGetGeneratedKeys = false; + this.selectLastInsertedId = null; + + } else { + // insert sql for db identity or sequence insert + this.concatinatedKey = false; + this.identityDbColumns = new String[]{id.getIdentityColumn()}; + this.sqlNullId = genSql(true, null); + this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys(); + this.selectLastInsertedId = desc.getSelectLastInsertedId(); + } + } + + private static Bindable getDiscriminator(BeanDescriptor desc){ + InheritInfo inheritInfo = desc.getInheritInfo(); + if (inheritInfo != null){ + return new BindableDiscriminator(inheritInfo); + } else { + return null; + } + } + + /** + * Return true if empty strings should be treated as null. + */ + public boolean isEmptyStringToNull() { + return emptyStringToNull; + } + + /** + * Return true if this is a concatenated key. + */ + public boolean isConcatinatedKey() { + return concatinatedKey; + } + + public String[] getIdentityDbColumns() { + return identityDbColumns; + } + + /** + * Returns sql that is used to fetch back the last inserted id. This will + * return null if it should not be used. + *

    + * This is only for DB's that do not support getGeneratedKeys. For MS + * SQLServer 2000 this could return "SELECT (at)(at)IDENTITY as id". + *

    + */ + public String getSelectLastInsertedId() { + return selectLastInsertedId; + } + + /** + * Return true if getGeneratedKeys is supported by the underlying jdbc + * driver and database. + */ + public boolean supportsGetGeneratedKeys() { + return supportsGetGeneratedKeys; + } + + /** + * Return true if the Id can be derived from other property values. + */ + public boolean deriveConcatenatedId(PersistRequestBean persist) { + return id.deriveConcatenatedId(persist); + } + + /** + * Bind the request based on whether the id value(s) are null. + */ + public void bind(DmlHandler request, Object bean, boolean withId) throws SQLException { + + if (withId) { + id.dmlBind(request, false, bean); + } + if (shadowFKey != null){ + shadowFKey.dmlBind(request, false, bean); + } + if (discriminator != null){ + discriminator.dmlBind(request, false, bean); + } + all.dmlBind(request, false, bean); + } + + /** + * get the sql based whether the id value(s) are null. + */ + public String getSql(boolean withId) { + + if (withId) { + return sqlWithId; + } else { + return sqlNullId; + } + } + + private String genSql(boolean nullId, Set loadedProps) { + + GenerateDmlRequest request = new GenerateDmlRequest(emptyStringToNull, loadedProps, null); + request.setInsertSetMode(); + + request.append("insert into ").append(tableName); + request.append(" ("); + + if (!nullId) { + id.dmlInsert(request, false); + } + + if (shadowFKey != null){ + shadowFKey.dmlInsert(request, false); + } + + if (discriminator != null){ + discriminator.dmlInsert(request, false); + } + + all.dmlInsert(request, false); + + request.append(") values ("); + request.append(request.getInsertBindBuffer()); + request.append(")"); + + return request.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java new file mode 100644 index 000000000..2c7cd0a06 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java @@ -0,0 +1,146 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.DbEncrypt; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableUnidirectional; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryAssocOnes; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryBaseProperties; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryEmbedded; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryId; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryVersion; + +/** + * Factory for creating InsertMeta UpdateMeta and DeleteMeta. + */ +public class MetaFactory { + + private final FactoryBaseProperties baseFact; + private final FactoryEmbedded embeddedFact; + private final FactoryVersion versionFact = new FactoryVersion(); + private final FactoryAssocOnes assocOneFact = new FactoryAssocOnes(); + + private final FactoryId idFact = new FactoryId(); + + /** + * Include Lobs in the base statement. Generally true. Oracle9 used to require + * a separate statement for Clobs and Blobs. + */ + private static final boolean includeLobs = true; + + private final DatabasePlatform dbPlatform; + + private final boolean emptyStringAsNull; + + public MetaFactory(DatabasePlatform dbPlatform) { + this.dbPlatform = dbPlatform; + this.emptyStringAsNull = dbPlatform.isTreatEmptyStringsAsNull(); + + // to bind encryption data before or after the encryption key + DbEncrypt dbEncrypt = dbPlatform.getDbEncrypt(); + boolean bindEncryptDataFirst = dbEncrypt == null ? true : dbEncrypt.isBindEncryptDataFirst(); + + this.baseFact = new FactoryBaseProperties(bindEncryptDataFirst); + this.embeddedFact = new FactoryEmbedded(bindEncryptDataFirst); + } + + /** + * Create the UpdateMeta for the given bean type. + */ + public UpdateMeta createUpdate(BeanDescriptor desc) { + + List setList = new ArrayList(); + + baseFact.create(setList, desc, DmlMode.UPDATE, includeLobs); + embeddedFact.create(setList, desc, DmlMode.UPDATE, includeLobs); + assocOneFact.create(setList, desc, DmlMode.UPDATE); + + BindableId id = idFact.createId(desc); + + Bindable ver = versionFact.create(desc); + + List allList = new ArrayList(); + + baseFact.create(allList, desc, DmlMode.WHERE, false); + embeddedFact.create(allList, desc, DmlMode.WHERE, false); + assocOneFact.create(allList, desc, DmlMode.WHERE); + + Bindable setBindable = new BindableList(setList); + Bindable allBindable = new BindableList(allList); + + return new UpdateMeta(emptyStringAsNull, desc, setBindable, id, ver, allBindable); + } + + /** + * Create the DeleteMeta for the given bean type. + */ + public DeleteMeta createDelete(BeanDescriptor desc) { + + BindableId id = idFact.createId(desc); + + Bindable ver = versionFact.create(desc); + + List allList = new ArrayList(); + + baseFact.create(allList, desc, DmlMode.WHERE, false); + embeddedFact.create(allList, desc, DmlMode.WHERE, false); + assocOneFact.create(allList, desc, DmlMode.WHERE); + + Bindable allBindable = new BindableList(allList); + + return new DeleteMeta(emptyStringAsNull, desc, id, ver, allBindable); + } + + /** + * Create the InsertMeta for the given bean type. + */ + public InsertMeta createInsert(BeanDescriptor desc) { + + BindableId id = idFact.createId(desc); + + List allList = new ArrayList(); + + baseFact.create(allList, desc, DmlMode.INSERT, includeLobs); + embeddedFact.create(allList, desc, DmlMode.INSERT, includeLobs); + assocOneFact.create(allList, desc, DmlMode.INSERT); + + Bindable allBindable = new BindableList(allList); + + BeanPropertyAssocOne unidirectional = desc.getUnidirectional(); + + Bindable shadowFkey; + if (unidirectional == null) { + shadowFkey = null; + } else { + shadowFkey = new BindableUnidirectional(desc, unidirectional); + } + + return new InsertMeta(dbPlatform, desc, shadowFkey, id, allBindable); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java new file mode 100644 index 000000000..ccd1de207 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; + +/** + * Implementation API for insert update and delete handlers. + */ +public interface PersistHandler { + + /** + * Return the bind log. + */ + public String getBindLog(); + + /** + * Get the sql and bind the statement. + */ + public void bind() throws SQLException; + + /** + * Add this for batch execution. + */ + public void addBatch() throws SQLException; + + /** + * Execute now for non-batch execution. + */ + public void execute() throws SQLException; + + /** + * Close resources including underlying preparedStatement. + */ + public void close() throws SQLException; +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java new file mode 100644 index 000000000..fcffedf19 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Set; + +import javax.persistence.OptimisticLockException; + +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiUpdatePlan; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Update bean handler. + */ +public class UpdateHandler extends DmlHandler { + + + private final UpdateMeta meta; + + private Set updatedProperties; + + private boolean emptySetClause; + + public UpdateHandler(PersistRequestBean persist, UpdateMeta meta) { + super(persist, meta.isEmptyStringAsNull()); + this.meta = meta; + } + + /** + * Generate and bind the update statement. + */ + public void bind() throws SQLException { + + SpiUpdatePlan updatePlan = meta.getUpdatePlan(persistRequest); + + if (updatePlan.isEmptySetClause()) { + emptySetClause = true; + return; + } + + updatedProperties = updatePlan.getProperties(); + + sql = updatePlan.getSql(); + + SpiTransaction t = persistRequest.getTransaction(); + boolean isBatch = t.isBatchThisRequest(); + + PreparedStatement pstmt; + if (isBatch) { + pstmt = getPstmt(t, sql, persistRequest, false); + + } else { + logSql(sql); + pstmt = getPstmt(t, sql, false); + } + dataBind = new DataBind(pstmt); + + bindLogAppend("Binding Update ["); + bindLogAppend(meta.getTableName()); + bindLogAppend("] "); + + meta.bind(persistRequest, this, updatePlan); + + setUpdateGenValues(); + + bindLogAppend("]"); + logBinding(); + } + + @Override + public void addBatch() throws SQLException { + if (!emptySetClause){ + super.addBatch(); + } + } + + /** + * Execute the update in non-batch. + */ + @Override + public void execute() throws SQLException, OptimisticLockException { + if (!emptySetClause){ + int rowCount = dataBind.executeUpdate(); + checkRowCount(rowCount); + setAdditionalProperties(); + } + } + + @Override + public boolean isIncluded(BeanProperty prop) { + + return prop.isDbUpdatable() && (updatedProperties == null || updatedProperties.contains(prop.getName())); + } + + public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { + persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java new file mode 100644 index 000000000..3d614e17d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java @@ -0,0 +1,259 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiUpdatePlan; +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList; + +/** + * Meta data for update handler. The meta data is for a particular bean type. It + * is considered immutable and is thread safe. + */ +public final class UpdateMeta { + + private final String sqlVersion; + + private final String sqlNone; + + private final Bindable set; + private final BindableId id; + private final Bindable version; + private final Bindable all; + + private final String tableName; + + private final UpdatePlan modeNoneUpdatePlan; + private final UpdatePlan modeVersionUpdatePlan; + + private final boolean emptyStringAsNull; + + public UpdateMeta(boolean emptyStringAsNull, BeanDescriptor desc, Bindable set, BindableId id, Bindable version, Bindable all) { + this.emptyStringAsNull = emptyStringAsNull; + this.tableName = desc.getBaseTable(); + this.set = set; + this.id = id; + this.version = version; + this.all = all; + + this.sqlNone = genSql(ConcurrencyMode.NONE, null, null); + this.sqlVersion = genSql(ConcurrencyMode.VERSION, null, null); + + this.modeNoneUpdatePlan = new UpdatePlan(ConcurrencyMode.NONE, sqlNone, set); + this.modeVersionUpdatePlan = new UpdatePlan(ConcurrencyMode.VERSION, sqlVersion, set); + + } + + /** + * Return true if empty strings should be treated as null. + */ + public boolean isEmptyStringAsNull() { + return emptyStringAsNull; + } + + /** + * Return the base table name. + */ + public String getTableName() { + return tableName; + } + + /** + * Bind the request based on the concurrency mode. + */ + public void bind(PersistRequestBean persist, DmlHandler bind, SpiUpdatePlan updatePlan) throws SQLException { + + Object bean = persist.getBean(); + + bind.bindLogAppend(" set["); + // bind.setCheckDelta(true); + updatePlan.bindSet(bind, bean); + // bind.setCheckDelta(false); + + bind.bindLogAppend("] where["); + id.dmlBind(bind, false, bean); + + switch (persist.getConcurrencyMode()) { + case VERSION: + version.dmlBind(bind, false, bean); + break; + case ALL: + Object oldBean = persist.getOldValues(); + all.dmlBindWhere(bind, true, oldBean); + break; + + default: + break; + } + } + + /** + * get or generate the sql based on the concurrency mode. + */ + public SpiUpdatePlan getUpdatePlan(PersistRequestBean request) { + + ConcurrencyMode mode = request.determineConcurrencyMode(); + if (request.isDynamicUpdateSql()) { + return getDynamicUpdatePlan(mode, request); + } + + // 'full bean' update... + switch (mode) { + case NONE: + return modeNoneUpdatePlan; + + case VERSION: + return modeVersionUpdatePlan; + + case ALL: + Object oldValues = request.getOldValues(); + if (oldValues == null) { + throw new PersistenceException("OldValues are null?"); + } + String sql = genDynamicWhere(request.getUpdatedProperties(), request.getLoadedProperties(), oldValues); + return new UpdatePlan(ConcurrencyMode.ALL, sql, set); + + default: + throw new RuntimeException("Invalid mode " + mode); + } + } + + private SpiUpdatePlan getDynamicUpdatePlan(ConcurrencyMode mode, PersistRequestBean persistRequest) { + + Set updatedProps = persistRequest.getUpdatedProperties(); + + if (ConcurrencyMode.ALL.equals(mode)) { + // due to is null in where clause we won't bother trying to + // cache plans for ConcurrencyMode.ALL + String sql = genSql(mode, persistRequest, null); + if (sql == null) { + // changed properties must have been updatable=false + return UpdatePlan.EMPTY_SET_CLAUSE; + } else { + return new UpdatePlan(null, mode, sql, set, updatedProps); + } + } + + // we can use a cached UpdatePlan for the changed properties + int hash = mode.hashCode(); + hash = hash * 31 + (updatedProps == null ? 0 : updatedProps.hashCode()); + Integer key = Integer.valueOf(hash); + + BeanDescriptor beanDescriptor = persistRequest.getBeanDescriptor(); + SpiUpdatePlan updatePlan = beanDescriptor.getUpdatePlan(key); + if (updatePlan != null) { + return updatePlan; + } + + // build a new UpdatePlan and cache it + + // build a bindableList that only contains the changed properties + List list = new ArrayList(); + set.addChanged(persistRequest, list); + BindableList bindableList = new BindableList(list); + + // build the SQL for this update statement + String sql = genSql(mode, persistRequest, bindableList); + + updatePlan = new UpdatePlan(key, mode, sql, bindableList, null); + + // add the UpdatePlan to the cache + beanDescriptor.putUpdatePlan(key, updatePlan); + + return updatePlan; + } + + private String genSql(ConcurrencyMode conMode, PersistRequestBean persistRequest, BindableList bindableList) { + + // update set col0=?, col1=?, col2=? where bcol=? and bc1=? and bc2=? + + GenerateDmlRequest request; + if (persistRequest == null) { + // For generation of None and Version DML/SQL + request = new GenerateDmlRequest(emptyStringAsNull); + } else { + request = persistRequest.createGenerateDmlRequest(emptyStringAsNull); + } + + request.append("update ").append(tableName).append(" set "); + + request.setUpdateSetMode(); + if (bindableList != null) { + bindableList.dmlAppend(request, false); + } else { + set.dmlAppend(request, true); + } + + if (request.getBindColumnCount() == 0) { + // update properties must have been updatable=false + // with the result that nothing is in the set clause + return null; + } + + request.append(" where "); + + request.setWhereIdMode(); + id.dmlAppend(request, false); + + if (ConcurrencyMode.VERSION.equals(conMode)) { + if (version == null) { + return null; + } + version.dmlAppend(request, false); + + } else if (ConcurrencyMode.ALL.equals(conMode)) { + + all.dmlWhere(request, true, request.getOldValues()); + } + + return request.toString(); + } + + /** + * Generate the sql dynamically for where using IS NULL for binding null + * values. + */ + private String genDynamicWhere(Set loadedProps, Set whereProps, Object oldBean) { + + // always has a preceding id property(s) so the first + // option is always ' and ' and not blank. + + GenerateDmlRequest request = new GenerateDmlRequest(emptyStringAsNull, loadedProps, whereProps, oldBean); + + request.append(sqlNone); + + request.setWhereMode(); + all.dmlWhere(request, true, oldBean); + + return request.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java new file mode 100644 index 000000000..b3410cfc0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java @@ -0,0 +1,172 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; +import java.util.Set; + +import com.avaje.ebeaninternal.api.SpiUpdatePlan; +import com.avaje.ebeaninternal.server.core.ConcurrencyMode; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; + +/** + * Cachable plan for executing bean updates for a given set of changed + * properties. + * + * @author rbygrave + */ +public class UpdatePlan implements SpiUpdatePlan { + + /** + * Special plan used when there is nothing in the set clause and the update + * should in fact be skipped. Occurs when the updated properties have + * updatable=false in their deployment. + */ + public static final UpdatePlan EMPTY_SET_CLAUSE = new UpdatePlan(); + + private final Integer key; + + private final ConcurrencyMode mode; + + private final String sql; + + private final Bindable set; + + private final Set properties; + + private final boolean checkIncludes; + + private final long timeCreated; + + private final boolean emptySetClause; + + private Long timeLastUsed; + + /** + * Create a non cachable UpdatePlan. + */ + public UpdatePlan(ConcurrencyMode mode, String sql, Bindable set) { + + this(null, mode, sql, set, null); + } + + /** + * Create a cachable UpdatePlan with a given key. + */ + public UpdatePlan(Integer key, ConcurrencyMode mode, String sql, + Bindable set, Set properties) { + + this.emptySetClause = false; + this.key = key; + this.mode = mode; + this.sql = sql; + this.set = set; + this.properties = properties; + this.checkIncludes = properties != null; + this.timeCreated = System.currentTimeMillis(); + } + + /** + * Special constructor for emptySetClause=true instance. + */ + private UpdatePlan(){ + this.emptySetClause = true; + this.key = Integer.valueOf(0); + this.mode = ConcurrencyMode.NONE; + this.sql = null; + this.set = null; + this.properties = null; + this.checkIncludes = false; + this.timeCreated = 0; + } + + + public boolean isEmptySetClause() { + return emptySetClause; + } + + /** + * Run the prepared statement binding for the 'update set' properties. + */ + public void bindSet(DmlHandler bind, Object bean) throws SQLException { + + set.dmlBind(bind, checkIncludes, bean); + + // not strictly 'thread safe' but object assignment is atomic + Long touched = Long.valueOf(System.currentTimeMillis()); + this.timeLastUsed = touched; + } + + /** + * Return the time this plan was created. + */ + public long getTimeCreated() { + return timeCreated; + } + + /** + * Return the time this plan was last used. + */ + public Long getTimeLastUsed() { + + // not thread safe but atomic + return timeLastUsed; + } + + /** + * Return the hash key. + */ + public Integer getKey() { + return key; + } + + /** + * Return the concurrency mode for this plan. + */ + public ConcurrencyMode getMode() { + return mode; + } + + /** + * Return the DML statement. + */ + public String getSql() { + return sql; + } + + /** + * Return the Bindable properties for the update set. + */ + public Bindable getSet() { + return set; + } + + /** + * Return the set of changed properties. + *

    + * This can return null when all properties in the set are being bound in + * the update statement. + *

    + */ + public Set getProperties() { + return properties; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/package.html b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/package.html new file mode 100644 index 000000000..05ba5dce1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/package.html @@ -0,0 +1,10 @@ + + + + Bean DML persistence implementation + + +Bean DML persistence implementation + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java new file mode 100644 index 000000000..21ebf8dec --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Item held by Meta objects used to generate and bind bean insert update and + * delete statements. + *

    + * An implementation is expected to be immutable and thread safe. + *

    + *

    + * The design is to take a bean structure with embedded and associated objects + * etc and flatten that into lists of Bindable objects. These are put into + * InsertMeta UpdateMeta and DeleteMeta objects to support the generation of DML + * and binding of statements in a fast and painless manor. + *

    + */ +public interface Bindable { + + /** + * For Updates including only changed properties add the Bindable to the + * list if it should be included in the 'update set'. + */ + public void addChanged(PersistRequestBean request, List list); + + /** + * append sql to the buffer with prefix and suffix options. + */ + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes); + + /** + * append sql to the buffer with prefix and suffix options. + */ + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes); + + /** + * For WHERE clauses append sql to the buffer with prefix and suffix + * options. These need to take into account binding of null values. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean); + + /** + * Bind given the request and bean. The bean could be the oldValues bean + * when binding a update or delete where clause with ALL concurrency mode. + */ + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) + throws SQLException; + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) + throws SQLException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java new file mode 100644 index 000000000..383029776 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for an ManyToOne or OneToOne associated bean. + */ +public class BindableAssocOne implements Bindable { + + private final BeanPropertyAssocOne assocOne; + + private final ImportedId importedId; + + public BindableAssocOne(BeanPropertyAssocOne assocOne) { + this.assocOne = assocOne; + this.importedId = assocOne.getImportedId(); + } + + public String toString() { + return "BindableAssocOne " + assocOne; + } + + public void addChanged(PersistRequestBean request, List list) { + if (request.hasChanged(assocOne)) { + list.add(this); + } + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + if (checkIncludes && !request.isIncluded(assocOne)) { + return; + } + importedId.dmlAppend(request); + } + + /** + * Used for dynamic where clause generation. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + if (checkIncludes && !request.isIncludedWhere(assocOne)) { + return; + } + Object assocBean = assocOne.getValue(bean); + importedId.dmlWhere(request, assocBean); + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + if (checkIncludes && !request.isIncluded(assocOne)) { + return; + } + dmlBind(request, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + if (checkIncludes && !request.isIncludedWhere(assocOne)) { + return; + } + dmlBind(request, bean, false); + } + + private void dmlBind(BindableRequest request, Object bean, boolean bindNull) + throws SQLException { + + Object assocBean = assocOne.getValue(bean); + Object boundValue = importedId.bind(request, assocBean, bindNull); + if (bindNull && boundValue == null && assocBean != null){ + // this is the scenario for a derived foreign key + // which will require an additional update + // register for post insert of assocBean + // update of bean set ... importedId.getLogicalName(); + // value of assocBean.getId + DerivedRelationshipData d = new DerivedRelationshipData(assocBean, assocOne.getName(), bean); + request.registerDerivedRelationship(d); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java new file mode 100644 index 000000000..5735adfd3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java @@ -0,0 +1,106 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a Immutable Compound value object. + */ +public class BindableCompound implements Bindable { + + private final Bindable[] items; + + private final BeanPropertyCompound compound; + + public BindableCompound(BeanPropertyCompound embProp, List list) { + this.compound = embProp; + this.items = list.toArray(new Bindable[list.size()]); + } + + public String toString() { + return "BindableCompound " + compound + " items:" + Arrays.toString(items); + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + if (checkIncludes && !request.isIncluded(compound)) { + return; + } + + for (int i = 0; i < items.length; i++) { + items[i].dmlAppend(request, false); + } + } + + /** + * Used for dynamic where clause generation. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object origBean) { + if (checkIncludes && !request.isIncludedWhere(compound)) { + return; + } + + Object valueObject = compound.getValue(origBean); + + for (int i = 0; i < items.length; i++) { + items[i].dmlWhere(request, false, valueObject); + } + } + + public void addChanged(PersistRequestBean request, List list) { + if (request.hasChanged(compound)) { + list.add(this); + } + } + + public void dmlBind(BindableRequest bindRequest, boolean checkIncludes, Object bean) throws SQLException { + if (checkIncludes && !bindRequest.isIncluded(compound)) { + return; + } + + Object valueObject = compound.getValue(bean); + + for (int i = 0; i < items.length; i++) { + items[i].dmlBind(bindRequest, false, valueObject); + } + } + + public void dmlBindWhere(BindableRequest bindRequest, boolean checkIncludes, Object bean) throws SQLException { + if (checkIncludes && !bindRequest.isIncludedWhere(compound)) { + return; + } + + Object valueObject = compound.getValue(bean); + + for (int i = 0; i < items.length; i++) { + items[i].dmlBindWhere(bindRequest, false, valueObject); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java new file mode 100644 index 000000000..1c70a136c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for inserting a discriminator value. + */ +public class BindableDiscriminator implements Bindable { + + private final String columnName; + private final Object discValue; + private final int sqlType; + + public BindableDiscriminator(InheritInfo inheritInfo) { + this.columnName = inheritInfo.getDiscriminatorColumn(); + this.discValue = inheritInfo.getDiscriminatorValue(); + this.sqlType = inheritInfo.getDiscriminatorType(); + } + + public String toString() { + return columnName + " = " + discValue; + } + + public void addChanged(PersistRequestBean request, List list) { + throw new PersistenceException("Never called (only for inserts)"); + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + /** + * Never used in where clause. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + // never used in where + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + request.appendColumn(columnName); + } + + public void dmlBind(BindableRequest bindRequest, boolean checkIncludes, Object bean) throws SQLException { + + bindRequest.bind(columnName, discValue, sqlType); + } + + public void dmlBindWhere(BindableRequest bindRequest, boolean checkIncludes, Object bean) throws SQLException { + + bindRequest.bind(columnName, discValue, sqlType); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java new file mode 100644 index 000000000..dab9950f4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java @@ -0,0 +1,139 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a Embedded bean. + */ +public class BindableEmbedded implements Bindable { + + private final Bindable[] items; + + private final BeanPropertyAssocOne embProp; + + public BindableEmbedded(BeanPropertyAssocOne embProp, List list) { + this.embProp = embProp; + this.items = list.toArray(new Bindable[list.size()]); + } + + public String toString() { + return "BindableEmbedded " + embProp + " items:" + Arrays.toString(items); + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + if (checkIncludes && !request.isIncluded(embProp)) { + return; + } + + for (int i = 0; i < items.length; i++) { + items[i].dmlAppend(request, false); + } + } + + /** + * Used for dynamic where clause generation. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object origBean) { + if (checkIncludes && !request.isIncludedWhere(embProp)) { + return; + } + Object embBean = embProp.getValue(origBean); + Object oldValues = getOldValue(embBean); + + for (int i = 0; i < items.length; i++) { + items[i].dmlWhere(request, false, oldValues); + } + } + + public void addChanged(PersistRequestBean request, List list) { + if (request.hasChanged(embProp)) { + list.add(this); + } + } + + public void dmlBind(BindableRequest bindRequest, boolean checkIncludes, Object bean) + throws SQLException { + + if (checkIncludes && !bindRequest.isIncluded(embProp)) { + return; + } + + // get the embedded bean + Object embBean = embProp.getValue(bean); + + for (int i = 0; i < items.length; i++) { + items[i].dmlBind(bindRequest, false, embBean); + } + } + + public void dmlBindWhere(BindableRequest bindRequest, boolean checkIncludes, Object bean) + throws SQLException { + + if (checkIncludes && !bindRequest.isIncludedWhere(embProp)) { + return; + } + + // get the embedded bean + Object embBean = embProp.getValue(bean); + Object oldEmbBean = getOldValue(embBean); + + for (int i = 0; i < items.length; i++) { + items[i].dmlBindWhere(bindRequest, false, oldEmbBean); + } + } + + /** + * Get the old bean which will have the original values. + *

    + * These are bound to the WHERE clause for updates. + *

    + */ + private Object getOldValue(Object embBean) { + + Object oldValues = null; + + if (embBean instanceof EntityBean) { + // get the old embedded bean (with the original values) + oldValues = ((EntityBean) embBean)._ebean_getIntercept().getOldValues(); + } + + if (oldValues == null) { + // this embedded bean was not modified + // (or not an EntityBean) + oldValues = embBean; + } + + return oldValues; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java new file mode 100644 index 000000000..af00059b7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java @@ -0,0 +1,144 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a DB encrypted BeanProperty. + */ +public class BindableEncryptedProperty implements Bindable { + + private final BeanProperty prop; + + private final boolean bindEncryptDataFirst; + + public BindableEncryptedProperty(BeanProperty prop, boolean bindEncryptDataFirst) { + this.prop = prop; + this.bindEncryptDataFirst = bindEncryptDataFirst; + } + + public String toString() { + return prop.toString(); + } + + public void addChanged(PersistRequestBean request, List list) { + if (request.hasChanged(prop)) { + list.add(this); + } + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + + if (checkIncludes && !request.isIncluded(prop)) { + return; + } + // columnName + // AES_ENCRYPT(?,?) + request.appendColumn(prop.getDbColumn(), prop.getDbBind()); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + + if (checkIncludes && !request.isIncluded(prop)) { + return; + } + // columnName = AES_ENCRYPT(?,?) + request.appendColumn(prop.getDbColumn(), "=", prop.getDbBind()); + } + + /** + * Used for dynamic where clause generation. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + // only include encrypted property in where when it is included + // in the update as well (so not using isIncludedWhere) + if (checkIncludes && !request.isIncluded(prop)) { + return; + } + + if (bean == null || request.isDbNull(prop.getValue(bean))) { + request.appendColumnIsNull(prop.getDbColumn()); + + } else { + // ? = AES_DECRYPT(columnName,?) + request.appendColumn("? = ", prop.getDecryptSql()); + } + } + + /** + * Bind a value in a Insert SET clause. + */ + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) + throws SQLException { + + if (checkIncludes && !request.isIncluded(prop)) { + return; + } + Object value = null; + if (bean != null) { + value = prop.getValue(bean); + } + + // get Encrypt key + String encryptKeyValue = prop.getEncryptKey().getStringValue(); + + if (!bindEncryptDataFirst){ + // H2 encrypt function ... different parameter order + request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****"); + } + request.bindNoLog(value, prop, prop.getName(), true); + + if (bindEncryptDataFirst){ + // MySql, Postgres, Oracle + request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****"); + } + + } + + + /** + * Bind a value in a Insert SET clause. + */ + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) + throws SQLException { + + if (checkIncludes && !request.isIncluded(prop)) { + return; + } + Object value = null; + if (bean != null) { + value = prop.getValue(bean); + } + + // get Encrypt key + String encryptKeyValue = prop.getEncryptKey().getStringValue(); + + request.bind(value, prop, prop.getName(), false); + request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****"); + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java new file mode 100644 index 000000000..86ad3d5ee --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; + +/** + * Adds support for id creation for concatenated ids on intersection tables. + *

    + * Specifically if the concatenated id object is null on insert this can be + * built from the matching ManyToOne associated beans. For example RoleUserId + * embeddedId object could be built from the associated Role and User beans. + *

    + *

    + * This is only attempted if the id is null when it gets to the insert. + *

    + */ +public interface BindableId extends Bindable { + + /** + * Return true if there is no Id properties at all. + */ + public boolean isEmpty(); + + /** + * Return true if this is a concatenated key. + */ + public boolean isConcatenated(); + + /** + * Return the DB Column to use with genGeneratedKeys. + */ + public String getIdentityColumn(); + + /** + * Create the concatenated id for inserts with PFK relationships. + *

    + * Really only where there are ManyToOne assoc beans that make up the + * primary key and the values can be got from those. + *

    + */ + public boolean deriveConcatenatedId(PersistRequestBean persist); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java new file mode 100644 index 000000000..05b47a6eb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java @@ -0,0 +1,148 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import javax.persistence.PersistenceException; + +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.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a EmbeddedId. + */ +public final class BindableIdEmbedded implements BindableId { + + private final BeanPropertyAssocOne embId; + + private final BeanProperty[] props; + + private final MatchedImportedProperty[] matches; + + public BindableIdEmbedded(BeanPropertyAssocOne embId, BeanDescriptor desc) { + this.embId = embId; + this.props = embId.getTargetDescriptor().propertiesBaseScalar(); + matches = MatchedImportedProperty.build(props, desc); + } + + public boolean isEmpty() { + return false; + } + + public boolean isConcatenated() { + return true; + } + + public String getIdentityColumn() { + // return null for concatenated keys + return null; + } + + @Override + public String toString() { + return embId + " props:" + Arrays.toString(props); + } + + /** + * Does nothing for BindableId. + */ + public void addChanged(PersistRequestBean request, List list) { + // do nothing (id not changing) + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, false); + } + + private void dmlBind(BindableRequest bindRequest, boolean checkIncludes, Object bean, boolean bindNull) throws SQLException { + + if (checkIncludes && !bindRequest.isIncluded(embId)) { + return; + } + + Object idValue = embId.getValue(bean); + + for (int i = 0; i < props.length; i++) { + + Object value = props[i].getValue(idValue); + bindRequest.bind(value, props[i], props[i].getDbColumn(), bindNull); + } + + bindRequest.setIdValue(idValue); + } + + /** + * Id values are never null in where clause. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + if (checkIncludes && !request.isIncluded(embId)) { + return; + } + dmlAppend(request, false); + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + if (checkIncludes && !request.isIncluded(embId)) { + return; + } + for (int i = 0; i < props.length; i++) { + request.appendColumn(props[i].getDbColumn()); + } + } + + public boolean deriveConcatenatedId(PersistRequestBean persist) { + + if (matches == null) { + String m = "Matches for the concatinated key columns where not found?" + + " I expect that the concatinated key was null, and this bean does" + + " not have ManyToOne assoc beans matching the primary key columns?"; + throw new PersistenceException(m); + } + + Object bean = persist.getBean(); + + // create the new id + Object newId = embId.createEmbeddedId(); + + // populate it from the assoc one id values... + for (int i = 0; i < matches.length; i++) { + matches[i].populate(bean, newId); + } + + // support PropertyChangeSupport + embId.setValueIntercept(bean, newId); + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmpty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmpty.java new file mode 100644 index 000000000..b36d936d1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmpty.java @@ -0,0 +1,51 @@ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +public class BindableIdEmpty implements BindableId { + + public boolean isEmpty() { + return true; + } + + public void addChanged(PersistRequestBean request, List list) { + // nothing + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + // nothing + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + // nothing + } + + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + // nothing + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + // nothing + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + // nothing + } + + public boolean isConcatenated() { + return false; + } + + public String getIdentityColumn() { + return null; + } + + public boolean deriveConcatenatedId(PersistRequestBean persist) { + return false; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java new file mode 100644 index 000000000..c6e4dce1b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; + +import javax.persistence.PersistenceException; + +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.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a concatenated id that is not embedded. + */ +public final class BindableIdMap implements BindableId { + + private final BeanProperty[] uids; + + private final MatchedImportedProperty[] matches; + + public BindableIdMap(BeanProperty[] uids, BeanDescriptor desc) { + this.uids = uids; + matches = MatchedImportedProperty.build(uids, desc); + } + + public boolean isEmpty() { + return false; + } + + public boolean isConcatenated() { + return true; + } + + public String getIdentityColumn() { + // return null for concatenated keys + return null; + } + + @Override + public String toString() { + return Arrays.toString(uids); + } + + /** + * Does nothing for BindableId. + */ + public void addChanged(PersistRequestBean request, List list) { + // do nothing (id not changing) + } + + /** + * Id values are never null in where clause. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + // id values are never null in where clause + dmlAppend(request, false); + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + for (int i = 0; i < uids.length; i++) { + request.appendColumn(uids[i].getDbColumn()); + } + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, false); + } + + private void dmlBind(BindableRequest bindRequest, boolean checkIncludes, Object bean, boolean bindNull) throws SQLException { + + LinkedHashMap mapId = new LinkedHashMap(); + for (int i = 0; i < uids.length; i++) { + Object value = uids[i].getValue(bean); + + bindRequest.bind(value, uids[i], uids[i].getName(), bindNull); + + // putting logicalType into map rather than + // the dbType (which may have been converted). + mapId.put(uids[i].getName(), value); + } + bindRequest.setIdValue(mapId); + } + + public boolean deriveConcatenatedId(PersistRequestBean persist) { + + if (matches == null) { + String m = "Matches for the concatinated key columns where not found?" + + " I expect that the concatinated key was null, and this bean does" + + " not have ManyToOne assoc beans matching the primary key columns?"; + throw new PersistenceException(m); + } + + Object bean = persist.getBean(); + + // populate it from the assoc one id values... + for (int i = 0; i < matches.length; i++) { + matches[i].populate(bean, bean); + } + + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java new file mode 100644 index 000000000..5c7d2e5de --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java @@ -0,0 +1,108 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a single scalar id property. + */ +public final class BindableIdScalar implements BindableId { + + private final BeanProperty uidProp; + + public BindableIdScalar(BeanProperty uidProp) { + this.uidProp = uidProp; + } + + public boolean isEmpty() { + return false; + } + + public boolean isConcatenated() { + return false; + } + + public String getIdentityColumn() { + return uidProp.getDbColumn(); + } + + @Override + public String toString() { + return uidProp.toString(); + } + + /** + * Does nothing for BindableId. + */ + public void addChanged(PersistRequestBean request, List list) { + // do nothing (id not changing) + } + + /** + * Should not be called as this is really only for concatenated keys. + */ + public boolean deriveConcatenatedId(PersistRequestBean persist) { + throw new PersistenceException("Should not be called? only for concatinated keys"); + } + + /** + * Id values are never null in where clause. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + // id values are never null in where clause + request.appendColumn(uidProp.getDbColumn()); + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + + request.appendColumn(uidProp.getDbColumn()); + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, false); + } + + private void dmlBind(BindableRequest bindRequest, boolean checkIncludes, Object bean, boolean bindNull) throws SQLException { + + Object value = uidProp.getValue(bean); + + bindRequest.bind(value, uidProp, uidProp.getName(), bindNull); + + // used for summary logging + bindRequest.setIdValue(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java new file mode 100644 index 000000000..8857585cf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * List of Bindable items. + */ +public class BindableList implements Bindable { + + private final Bindable[] items; + + public BindableList(List list) { + items = list.toArray(new Bindable[list.size()]); + } + + public void addChanged(PersistRequestBean request, List list) { + for (int i = 0; i < items.length; i++) { + items[i].addChanged(request, list); + } + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + for (int i = 0; i < items.length; i++) { + items[i].dmlInsert(request, checkIncludes); + } + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + + for (int i = 0; i < items.length; i++) { + items[i].dmlAppend(request, checkIncludes); + } + } + + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + + for (int i = 0; i < items.length; i++) { + items[i].dmlWhere(request, checkIncludes, bean); + } + } + + public void dmlBind(BindableRequest bindRequest, boolean checkIncludes, Object bean) + throws SQLException { + + for (int i = 0; i < items.length; i++) { + items[i].dmlBind(bindRequest, checkIncludes, bean); + } + } + + public void dmlBindWhere(BindableRequest bindRequest, boolean checkIncludes, Object bean) + throws SQLException { + + for (int i = 0; i < items.length; i++) { + items[i].dmlBindWhere(bindRequest, checkIncludes, bean); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java new file mode 100644 index 000000000..7f4d3e070 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a single BeanProperty. + */ +public class BindableProperty implements Bindable { + + protected final BeanProperty prop; + + public BindableProperty(BeanProperty prop) { + this.prop = prop; + } + + public String toString() { + return prop.toString(); + } + + public void addChanged(PersistRequestBean request, List list) { + if (request.hasChanged(prop)) { + list.add(this); + } + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + if (checkIncludes && !request.isIncluded(prop)) { + return; + } + request.appendColumn(prop.getDbColumn()); + } + + /** + * Used for dynamic where clause generation. + */ + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + if (checkIncludes && !request.isIncludedWhere(prop)) { + return; + } + + if (bean == null || request.isDbNull(prop.getValue(bean))) { + request.appendColumnIsNull(prop.getDbColumn()); + + } else { + request.appendColumn(prop.getDbColumn()); + } + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + + if (checkIncludes && !request.isIncluded(prop)) { + return; + } + dmlBind(request, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + if (checkIncludes && !request.isIncludedWhere(prop)) { + return; + } + dmlBind(request, bean, false); + } + + private void dmlBind(BindableRequest request, Object bean, boolean bindNull) + throws SQLException { + + Object value = null; + if (bean != null) { + value = prop.getValue(bean); + } + // value = prop.getDefaultValue(); + request.bind(value, prop, prop.getName(), bindNull); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java new file mode 100644 index 000000000..d4dfb3961 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for insert on a property with a GeneratedProperty. + *

    + * This is typically a 'insert timestamp', 'update timestamp' or 'counter'. + *

    + */ +public class BindablePropertyInsertGenerated extends BindableProperty { + + private final GeneratedProperty gen; + + public BindablePropertyInsertGenerated(BeanProperty prop, GeneratedProperty gen) { + super(prop); + this.gen = gen; + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, false); + } + + /** + * Bind a value in a Insert SET clause. + */ + private void dmlBind(BindableRequest request, boolean checkIncludes, Object bean, boolean bindNull) throws SQLException { + + Object value = gen.getInsertValue(prop, bean); + + // generated value should be the correct type + if (bean != null){ + // support PropertyChangeSupport + prop.setValueIntercept(bean, value); + request.registerAdditionalProperty(prop.getName()); + } + //value = prop.getDefaultValue(); + request.bind(value, prop, prop.getName(), bindNull); + } + + /** + * Always bind on Insert SET. + */ + @Override + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes){ + request.appendColumn(prop.getDbColumn()); + } + +} 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 new file mode 100644 index 000000000..ec9c09397 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for update on a property with a GeneratedProperty. + *

    + * This is typically a 'update timestamp' or 'counter'. + *

    + */ +public class BindablePropertyUpdateGenerated extends BindableProperty { + + private final GeneratedProperty gen; + + public BindablePropertyUpdateGenerated(BeanProperty prop, GeneratedProperty gen) { + super(prop); + this.gen = gen; + } + + /** + * Always add BindablePropertyUpdateGenerated properties. + */ + public void addChanged(PersistRequestBean request, List list) { + + list.add(this); + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + if (checkIncludes && !request.isIncluded(prop)){ + return; + } + dmlBind(request, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + if (checkIncludes && !request.isIncludedWhere(prop)){ + return; + } + dmlBind(request, bean, false); + } + + private void dmlBind(BindableRequest request, Object bean, boolean bindNull) throws SQLException { + + Object value = gen.getUpdateValue(prop, bean); + + // generated value should be the correct type + request.bind(value, prop, prop.getName(), bindNull); + + // only register the update value if it was included + // in the bean in the first place + 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); + } + } + + /** + * Always bind on Insert SET. + */ + @Override + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes){ + if (checkIncludes && !request.isIncluded(prop)){ + return; + } + 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 new file mode 100644 index 000000000..afbe3d5f9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; + +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Request object passed to bindables. + */ +public interface BindableRequest { + + /** + * Set the id for use with summary level logging. + */ + public void setIdValue(Object idValue); + + /** + * Bind the value to a PreparedStatement. + *

    + * Takes into account logicalType to dbType conversion if required. + *

    + *

    + * Returns the value that was bound (and was potentially converted from + * logicalType to dbType. + *

    + * + * @param value + * the value of a property + * @param bindNull + * if true bind null values, if false use IS NULL. + */ + public Object bind(Object value, BeanProperty prop, String propName, boolean bindNull) throws SQLException; + + /** + * Bind a raw value. Used to bind the discriminator column. + */ + public Object bind(String propName, Object value, int sqlType) throws SQLException; + + /** + * Bind a raw value with a placeHolder to put into the transaction log. + */ + public Object bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException; + + /** + * Bind the value to the preparedStatement without logging. + */ + public Object bindNoLog(Object value, BeanProperty prop, String propName, boolean bindNull) throws SQLException; + + /** + * Return true if the property is included in this request. + */ + public boolean isIncluded(BeanProperty prop); + + /** + * Return true if the property is included in the WHERE clause for this + * request. + */ + public boolean isIncludedWhere(BeanProperty prop); + + /** + * Register the value from a update GeneratedValue. This can only be set to + * the bean property after the where clause has bean built. + */ + public void registerUpdateGenValue(BeanProperty prop, Object bean, Object value); + + /** + * Register a property into loadedProperties if required. + */ + public void registerAdditionalProperty(String propertyName); + + /** + * Return the original PersistRequest. + */ + public PersistRequestBean getPersistRequest(); + + public void registerDerivedRelationship(DerivedRelationshipData assocBean); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java new file mode 100644 index 000000000..8239f66eb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a unidirectional relationship. + *

    + * This inserts the foreign key value that is retrieved from the id of the + * parentBean. + *

    + */ +public class BindableUnidirectional implements Bindable { + + private final BeanPropertyAssocOne unidirectional; + + private final ImportedId importedId; + + private final BeanDescriptor desc; + + public BindableUnidirectional(BeanDescriptor desc, BeanPropertyAssocOne unidirectional) { + this.desc = desc; + this.unidirectional = unidirectional; + this.importedId = unidirectional.getImportedId(); + + } + + public String toString() { + return "BindableShadowFKey " + unidirectional; + } + + public void addChanged(PersistRequestBean request, List list) { + throw new PersistenceException("Never called (for insert only)"); + } + + public void dmlInsert(GenerateDmlRequest request, boolean checkIncludes) { + dmlAppend(request, checkIncludes); + } + + public void dmlAppend(GenerateDmlRequest request, boolean checkIncludes) { + // always included (in insert) + importedId.dmlAppend(request); + } + + public void dmlWhere(GenerateDmlRequest request, boolean checkIncludes, Object bean) { + throw new RuntimeException("Never called"); + } + + public void dmlBind(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, true); + } + + public void dmlBindWhere(BindableRequest request, boolean checkIncludes, Object bean) throws SQLException { + dmlBind(request, checkIncludes, bean, false); + } + + private void dmlBind(BindableRequest request, boolean checkIncludes, Object bean, boolean bindNull) + throws SQLException { + + PersistRequestBean persistRequest = request.getPersistRequest(); + Object parentBean = persistRequest.getParentBean(); + + if (parentBean == null) { + Class localType = desc.getBeanType(); + Class targetType = unidirectional.getTargetType(); + ; + String msg = "Error inserting bean [" + localType + "] with unidirectional relationship. "; + msg += "For inserts you must use cascade save on the master bean [" + targetType + "]."; + throw new PersistenceException(msg); + } + + importedId.bind(request, parentBean, bindNull); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java new file mode 100644 index 000000000..62b96ec0a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * A factory that builds Bindable for BeanPropertyAssocOne properties. + */ +public class FactoryAssocOnes { + + public FactoryAssocOnes() { + } + + /** + * Add foreign key columns from associated one beans. + */ + public List create(List list, BeanDescriptor desc, DmlMode mode) { + + BeanPropertyAssocOne[] ones = desc.propertiesOneImported(); + + for (int i = 0; i < ones.length; i++) { + if (ones[i].isImportedPrimaryKey()){ + // excluded as already part of the primary key + + } else { + switch (mode) { + case WHERE: + break; + case INSERT: + if (!ones[i].isInsertable()) { + continue; + } + break; + case UPDATE: + if (!ones[i].isUpdateable()) { + continue; + } + break; + } + list.add(new BindableAssocOne(ones[i])); + } + } + + return list; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java new file mode 100644 index 000000000..6ec30f922 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * Add base properties to the BindableList for a bean type. + *

    + * This excludes unique embedded and associated properties. + *

    + */ +public class FactoryBaseProperties { + + private final FactoryProperty factoryProperty; + + + public FactoryBaseProperties(boolean bindEncryptDataFirst) { + factoryProperty = new FactoryProperty(bindEncryptDataFirst); + } + + /** + * Add Bindable for the base properties to the list. + */ + public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { + + add(desc.propertiesBaseScalar(), list, desc, mode, withLobs); + + BeanPropertyCompound[] compoundProps = desc.propertiesBaseCompound(); + for (int i = 0; i < compoundProps.length; i++) { + BeanProperty[] props = compoundProps[i].getScalarProperties(); + + ArrayList newList = new ArrayList(props.length); + add(props, newList, desc, mode, withLobs); + + BindableCompound compoundBindable = new BindableCompound(compoundProps[i], newList); + + list.add(compoundBindable); + } + } + + private void add(BeanProperty[] props, List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { + + for (int i = 0; i < props.length; i++) { + + Bindable item = factoryProperty.create(props[i], mode, withLobs); + if (item != null) { + list.add(item); + } else { + // null where readOnly (Secondary tables) or Lob exclusion + } + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java new file mode 100644 index 000000000..042b09f2b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * A factory that builds Bindable for embedded bean properties. + */ +public class FactoryEmbedded { + + private final FactoryProperty factoryProperty; + + public FactoryEmbedded(boolean bindEncryptDataFirst) { + factoryProperty = new FactoryProperty(bindEncryptDataFirst); + } + + /** + * Add bindable for the embedded properties to the list. + */ + public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { + + BeanPropertyAssocOne[] embedded = desc.propertiesEmbedded(); + + for (int j = 0; j < embedded.length; j++) { + + List bindList = new ArrayList(); + + BeanProperty[] props = embedded[j].getProperties(); + for (int i = 0; i < props.length; i++) { + Bindable item = factoryProperty.create(props[i], mode, withLobs); + if (item != null){ + bindList.add(item); + } + } + + list.add(new BindableEmbedded(embedded[j], bindList)); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java new file mode 100644 index 000000000..65d99b86c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Create a Bindable for the ids of a bean type. + */ +public class FactoryId { + + public FactoryId() { + } + + /** + * Add uniqueId properties. + */ + public BindableId createId(BeanDescriptor desc) { + + BeanProperty[] uids = desc.propertiesId(); + if (uids.length == 0) { + return new BindableIdEmpty(); + + } else if (uids.length == 1) { + if (!uids[0].isEmbedded()) { + return new BindableIdScalar(uids[0]); + + } else { + BeanPropertyAssocOne embId = (BeanPropertyAssocOne) uids[0]; + return new BindableIdEmbedded(embId, desc); + } + } else { + return new BindableIdMap(uids, desc); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java new file mode 100644 index 000000000..b0bbe7cf9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * Creates the appropriate Bindable for a BeanProperty. + *

    + * Lob properties can be excluded and it creates BindablePropertyInsertGenerated + * and BindablePropertyUpdateGenerated as required. + *

    + */ +public class FactoryProperty { + + private final boolean bindEncryptDataFirst; + + public FactoryProperty(boolean bindEncryptDataFirst) { + this.bindEncryptDataFirst = bindEncryptDataFirst; + } + + /** + * Create a Bindable for the property given the mode and withLobs flag. + */ + public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs) { + + if (DmlMode.INSERT.equals(mode) && !prop.isDbInsertable()){ + return null; + } + if (DmlMode.UPDATE.equals(mode) && !prop.isDbUpdatable()){ + return null; + } + + if (prop.isLob()) { + if (DmlMode.WHERE.equals(mode) || !withLobs) { + // Lob exclusion + return null; + } else { + return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); + } + } + + GeneratedProperty gen = prop.getGeneratedProperty(); + if (gen != null) { + if (DmlMode.INSERT.equals(mode)) { + if (gen.includeInInsert()) { + return new BindablePropertyInsertGenerated(prop, gen); + } else { + return null; + } + + } + if (DmlMode.UPDATE.equals(mode)) { + if (gen.includeInUpdate()) { + return new BindablePropertyUpdateGenerated(prop, gen); + } else { + // An 'Insert Timestamp' is never updated + return null; + } + } + } + + return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java new file mode 100644 index 000000000..0329df693 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Creates a Bindable to support version concurrency where clauses. + */ +public class FactoryVersion { + + + public FactoryVersion() { + } + + /** + * Create a Bindable for the version property(s) for a bean type. + */ + public Bindable create(BeanDescriptor desc) { + + List verList = new ArrayList(); + + BeanProperty[] vers = desc.propertiesVersion(); + for (int i = 0; i < vers.length; i++) { + verList.add(new BindableProperty(vers[i])); + } + + // version columns on embedded beans? + BeanPropertyAssocOne[] embedded = desc.propertiesEmbedded(); + for (int j = 0; j < embedded.length; j++) { + + if (embedded[j].isEmbeddedVersion()) { + + List bindList = new ArrayList(); + + BeanProperty[] embProps = embedded[j].getProperties(); + + for (int i = 0; i < embProps.length; i++) { + if (embProps[i].isVersion()){ + bindList.add(new BindableProperty(embProps[i])); + } + } + + verList.add(new BindableEmbedded(embedded[j], bindList)); + } + } + + if (verList.size() == 0){ + return null; + } + if (verList.size() == 1){ + return verList.get(0); + } + + return new BindableList(verList); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java new file mode 100644 index 000000000..20fa62a2c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Matches local embedded id properties to 'matching' properties from a + * ManyToOne associated bean that is a 'imported primary key'. + *

    + * This object is designed to help BindableIdEmbedded and BindableIdMap to + * create a concatenated id from the id values from ManyToOne associated beans. + * This can be done when those ManyToOne associated beans make up the primary + * key. This typically means the BindableIdEmbedded is for a intersection table + * of a Many to Many relationship. + *

    + */ +class MatchedImportedProperty { + + private final BeanPropertyAssocOne assocOne; + + private final BeanProperty foreignProp; + + private final BeanProperty localProp; + + protected MatchedImportedProperty(BeanPropertyAssocOne assocOne, BeanProperty foreignProp, + BeanProperty localProp) { + this.assocOne = assocOne; + this.foreignProp = foreignProp; + this.localProp = localProp; + } + + protected void populate(Object sourceBean, Object destBean) { + Object assocBean = assocOne.getValue(sourceBean); + if (assocBean == null) { + String msg = "The assoc bean for " + assocOne + " is null?"; + throw new NullPointerException(msg); + } + + Object value = foreignProp.getValue(assocBean); + localProp.setValue(destBean, value); + } + + /** + * Create the array of matchedImportedProperty based on the properties and descriptor. + */ + protected static MatchedImportedProperty[] build(BeanProperty[] props, BeanDescriptor desc) { + + MatchedImportedProperty[] matches = new MatchedImportedProperty[props.length]; + + for (int i = 0; i < props.length; i++) { + // find matching assoc one property for dbColumn + matches[i] = MatchedImportedProperty.findMatch(props[i], desc); + if (matches[i] == null) { + // ok, the assoc ones are not on the bean? + return null; + } + } + return matches; + } + + private static MatchedImportedProperty findMatch(BeanProperty prop, BeanDescriptor desc) { + + // find matching against the local database column + String dbColumn = prop.getDbColumn(); + + BeanPropertyAssocOne[] assocOnes = desc.propertiesOne(); + for (int i = 0; i < assocOnes.length; i++) { + if (assocOnes[i].isImportedPrimaryKey()) { + + // search using the ImportedId from the assoc one + BeanProperty foreignMatch = assocOnes[i].getImportedId().findMatchImport(dbColumn); + + if (foreignMatch != null) { + return new MatchedImportedProperty(assocOnes[i], foreignMatch, prop); + } + } + } + + // there was no matching assoc one property. + // example UserRole bean missing assoc one to User? + return null; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/package.html b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/package.html new file mode 100644 index 000000000..829152eca --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/package.html @@ -0,0 +1,10 @@ + + + + Bindable objects to support Bean DML + + +Bindable objects to support Bean DML + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/package.html b/src/main/java/com/avaje/ebeaninternal/server/persist/package.html new file mode 100644 index 000000000..5faa44c3e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/package.html @@ -0,0 +1,15 @@ + + + + Persistence Implementation + + +Persistence Implementation + +

    +Implementing the saving and deleting of beans, and the executing of +updateable and callable sql. +

    + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/BackgroundFetch.java b/src/main/java/com/avaje/ebeaninternal/server/query/BackgroundFetch.java new file mode 100644 index 000000000..e57d554b9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/BackgroundFetch.java @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebeaninternal.api.SpiTransaction; + +/** + * Continue the fetch using a Background thread. The client knows when this has + * finished by checking to see if beanList.finishedFetch() is true. + */ +public class BackgroundFetch implements Callable { + + private static final Logger logger = Logger.getLogger(BackgroundFetch.class.getName()); + + private final CQuery cquery; + + private final SpiTransaction transaction; + + /** + * Create the BackgroundFetch. + */ + public BackgroundFetch(CQuery cquery) { + this.cquery = cquery; + this.transaction = cquery.getTransaction(); + } + + /** + * Continue the fetch. + */ + public Integer call() { + try { + + BeanCollection bc = cquery.continueFetchingInBackground(); + + return bc.size(); + + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + return Integer.valueOf(0); + + } finally { + try { + cquery.close(); + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + } + try { + // we must have our own transaction for background fetching + // and this performs the rollback... returning the + // connection back into the connection pool. + transaction.rollback(); + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + } + } + + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("BackgroundFetch ").append(cquery); + return sb.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/BackgroundIdFetch.java b/src/main/java/com/avaje/ebeaninternal/server/query/BackgroundIdFetch.java new file mode 100644 index 000000000..6b715bc52 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/BackgroundIdFetch.java @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; + +/** + * Continue the fetch using a Background thread. The client knows when this has + * finished by checking to see if beanList.finishedFetch() is true. + */ +public class BackgroundIdFetch implements Callable { + + private static final Logger logger = Logger.getLogger(BackgroundIdFetch.class.getName()); + + private final ResultSet rset; + + private final PreparedStatement pstmt; + + private final SpiTransaction transaction; + + private final DbReadContext ctx; + + private final BeanDescriptor beanDescriptor; + + private final BeanIdList idList; + /** + * Create the BackgroundFetch. + */ + public BackgroundIdFetch(SpiTransaction transaction, + ResultSet rset, PreparedStatement pstmt, + DbReadContext ctx, BeanDescriptor beanDescriptor, + BeanIdList idList) { + + this.ctx = ctx; + this.transaction = transaction; + this.rset = rset; + this.pstmt = pstmt; + this.beanDescriptor = beanDescriptor; + this.idList = idList; + } + + /** + * Continue the fetch. + */ + public Integer call() { + try { + int startSize = idList.getIdList().size(); + int rowsRead = 0; + while (rset.next()){ + Object idValue = beanDescriptor.getIdBinder().read(ctx); + idList.add(idValue); + ctx.getDataReader().resetColumnPosition(); + rowsRead++; + } + + if (logger.isLoggable(Level.INFO)){ + logger.info("BG FetchIds read:"+rowsRead+" total:"+(startSize+rowsRead)); + } + + return rowsRead; + + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + return 0; + + } finally { + try { + close(); + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + } + try { + // we must have our own transaction for background fetching + // and this performs the rollback... returning the + // connection back into the connection pool. + transaction.rollback(); + } catch (Exception e) { + logger.log(Level.SEVERE, null, e); + } + } + + } + + private void close() { + try { + if (rset != null) { + rset.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java b/src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java new file mode 100644 index 000000000..31adeba9e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A base object for query Future objects. + * + * @author rbygrave + * + * @param the entity bean type + */ +public abstract class BaseFuture implements Future { + + private final FutureTask futureTask; + + public BaseFuture(FutureTask futureTask) { + this.futureTask = futureTask; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + return futureTask.cancel(mayInterruptIfRunning); + } + + public T get() throws InterruptedException, ExecutionException { + return futureTask.get(); + } + + public T get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + + return futureTask.get(timeout, unit); + } + + public boolean isCancelled() { + return futureTask.isCancelled(); + } + + public boolean isDone() { + return futureTask.isDone(); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java new file mode 100644 index 000000000..25e88ab4e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java @@ -0,0 +1,220 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.Collection; +import java.util.Map; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.RelationalQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.util.BeanCollectionFactory; +import com.avaje.ebeaninternal.server.util.BeanCollectionParams; + +/** + * Wraps a BeanCollection with helper methods to add beans. + *

    + * Helps adding the bean to the underlying set list or map. + *

    + */ +public final class BeanCollectionWrapper { + + /** + * Flag set if this builds a Map rather than a Collection. + */ + private final boolean isMap; + + /** + * The type. + */ + private final SpiQuery.Type queryType; + + /** + * A property name used as key for a Map. + */ + private final String mapKey; + + /** + * The actual BeanCollection. + */ + private final BeanCollection beanCollection; + + /** + * Collection type of BeanCollection. + */ + private final Collection collection; + + /** + * Map type of BeanCollection. + */ + private final Map map; + + /** + * The associated BeanDescriptor. + */ + private final BeanDescriptor desc; + + /** + * The number of rows added. + */ + private int rowCount; + + public BeanCollectionWrapper(RelationalQueryRequest request) { + + this.desc = null; + this.queryType = request.getQueryType(); + this.mapKey = request.getQuery().getMapKey(); + this.isMap = SpiQuery.Type.MAP.equals(queryType); + + this.beanCollection = createBeanCollection(queryType); + this.collection = getCollection(isMap); + this.map = getMap(isMap); + } + + /** + * Create based on a Find. + */ + public BeanCollectionWrapper(OrmQueryRequest request) { + + this.desc = request.getBeanDescriptor(); + this.queryType = request.getQueryType(); + this.mapKey = request.getQuery().getMapKey(); + this.isMap = SpiQuery.Type.MAP.equals(queryType); + + this.beanCollection = createBeanCollection(queryType); + this.collection = getCollection(isMap); + this.map = getMap(isMap); + } + + /** + * Create based on a ManyType and mapKey. Note the mapKey is only used if + * the manyType is a Map. + *

    + * modifyListening is set to true if this is a collection used to hold + * ManyToMany associated objects. + *

    + */ + public BeanCollectionWrapper(BeanPropertyAssocMany manyProp) { + + this.queryType = manyProp.getManyType().getQueryType(); + this.mapKey = manyProp.getMapKey(); + this.desc = manyProp.getTargetDescriptor(); + this.isMap = SpiQuery.Type.MAP.equals(queryType); + + this.beanCollection = createBeanCollection(queryType); + this.collection = getCollection(isMap); + this.map = getMap(isMap); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Map getMap(boolean isMap) { + return isMap ? (Map)beanCollection : null; + } + + @SuppressWarnings("unchecked") + private Collection getCollection(boolean isMap) { + return isMap ? null : (Collection)beanCollection ; + } + + /** + * Return the underlying BeanCollection. + */ + public BeanCollection getBeanCollection() { + return beanCollection; + } + + /** + * Create a BeanCollection of the correct type. + */ + private BeanCollection createBeanCollection(SpiQuery.Type manyType) { + BeanCollectionParams p = new BeanCollectionParams(manyType); + return BeanCollectionFactory.create(p); + } + + /** + * Return true if this wraps a Map rather than a set or list. + */ + public boolean isMap() { + return isMap; + } + + /** + * Return the number of rows added to this wrapper. + */ + public int size() { + return rowCount; + } + + /** + * Add the bean to the collection held in this wrapper. + */ + public void add(Object bean) { + add(bean, beanCollection); + } + + /** + * Add the bean to the collection passed. + * + * @param bean + * the bean to add + * @param collection + * the collection or map to add the bean to + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void add(Object bean, Object collection) { + if (bean == null) { + return; + } + rowCount++; + if (isMap) { + Object keyValue = null; + if (mapKey != null) { + // use the value for the property + keyValue = desc.getValue(bean, mapKey); + } else { + // use the uniqueId for this + keyValue = desc.getId(bean); + } + + Map mapColl = (Map) collection; + mapColl.put(keyValue, bean); + } else { + ((Collection) collection).add(bean); + } + } + + /** + * Specifically add to a Collection. + */ + public void addToCollection(Object bean) { + collection.add(bean); + } + + /** + * Specifically add to this as a Map with a known key. + */ + public void addToMap(Object bean, Object key) { + map.put(key, bean); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java new file mode 100644 index 000000000..038848f28 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -0,0 +1,879 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.lang.ref.WeakReference; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryListener; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.NodeUsageCollector; +import com.avaje.ebean.bean.NodeUsageListener; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.LoadContext; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanCollectionHelp; +import com.avaje.ebeaninternal.server.deploy.BeanCollectionHelpFactory; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; +import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.DataReader; + +/** + * An object that represents a SqlSelect statement. + *

    + * The SqlSelect is based on a tree (Object Graph). The tree is traversed to see + * what parts are included in the tree according to the value of + * find.getInclude(); + *

    + *

    + * The tree structure is flattened into a SqlSelectChain. The SqlSelectChain is + * the key object used in reading the flat resultSet back into Objects. + *

    + */ +public class CQuery implements DbReadContext, CancelableQuery { + + private static final Logger logger = Logger.getLogger(CQuery.class.getName()); + + private static final int GLOBAL_ROW_LIMIT = 1000000; + + /** + * The resultSet rows read. + */ + private int rowCount; + + /** + * The number of master EntityBeans loaded. + */ + private int loadedBeanCount; + + /** + * Flag set when no more rows are in the resultSet. + */ + private boolean noMoreRows; + /** + * Id of loaded 'master' bean. + */ + private Object loadedBeanId; + /** + * Flag set when 'master' bean changed. + */ + boolean loadedBeanChanged; + /** + * The 'master' bean just loaded. + */ + private Object loadedBean; + + /** + * Holds the previous loaded bean. + */ + private Object prevLoadedBean; + + /** + * The detail bean just loaded. + */ + private Object loadedManyBean; + + /** + * The previous 'detail' collection remembered so that for manyToMany we can + * turn on the modify listening. + */ + private Object prevDetailCollection; + + /** + * The current 'detail' collection being populated. + */ + private Object currentDetailCollection; + + /** + * The 'master' collection being populated. + */ + private final BeanCollection collection; + /** + * The help for the 'master' collection. + */ + private final BeanCollectionHelp help; + + /** + * The overall find request wrapper object. + */ + private final OrmQueryRequest request; + + private final BeanDescriptor desc; + + private final SpiQuery query; + + private final QueryListener queryListener; + + private Map currentPathMap; + + private String currentPrefix; + + /** + * Flag set true when reading 'master' and 'detail' beans. + */ + private final boolean manyIncluded; + + /** + * Where clause predicates. + */ + private final CQueryPredicates predicates; + + /** + * Object handling the SELECT generation and reading. + */ + private final SqlTree sqlTree; + + private final boolean rawSql; + + /** + * The final sql that is generated. + */ + private final String sql; + + /** + * Where clause to show in logs when using an existing query plan. + */ + private final String logWhereSql; + + /** + * Set to true if the row number column is included in the sql. + */ + private final boolean rowNumberIncluded; + + /** + * Tree that knows how to build the master and detail beans from the + * resultSet. + */ + private final SqlTreeNode rootNode; + + /** + * For master detail query. + */ + private final BeanPropertyAssocMany manyProperty; + + /** + * The many property Expression language object. + */ + private final ElPropertyValue manyPropertyEl; + + private final int backgroundFetchAfter; + + private final int maxRowsLimit; + + /** + * Flag set when backgroundFetchAfter limit is hit. + */ + private boolean hasHitBackgroundFetchAfter; + + private final PersistenceContext persistenceContext; + + private DataReader dataReader; + + /** + * The statement used to create the resultSet. + */ + private PreparedStatement pstmt; + + private boolean cancelled; + + private String bindLog; + + private final CQueryPlan queryPlan; + + private long startNano; + + private final Mode queryMode; + + private final boolean autoFetchProfiling; + + private final ObjectGraphNode autoFetchParentNode; + + private final AutoFetchManager autoFetchManager; + private final WeakReference autoFetchManagerRef; + + private int executionTimeMicros; + + private final Boolean readOnly; + + private final SpiExpressionList filterMany; + + /** + * Create the Sql select based on the request. + */ + @SuppressWarnings("unchecked") + public CQuery(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan queryPlan) { + this.request = request; + this.queryPlan = queryPlan; + this.query = request.getQuery(); + this.queryMode = query.getMode(); + + this.readOnly = request.isReadOnly(); + + this.autoFetchManager = query.getAutoFetchManager(); + this.autoFetchProfiling = autoFetchManager != null; + this.autoFetchParentNode = autoFetchProfiling ? query.getParentNode() : null; + this.autoFetchManagerRef = autoFetchProfiling ? new WeakReference(autoFetchManager) : null; + + // set the generated sql back to the query + // so its available to the user... + query.setGeneratedSql(queryPlan.getSql()); + + this.sqlTree = queryPlan.getSqlTree(); + this.rootNode = sqlTree.getRootNode(); + + this.manyProperty = sqlTree.getManyProperty(); + this.manyPropertyEl = sqlTree.getManyPropertyEl(); + this.manyIncluded = sqlTree.isManyIncluded(); + if (manyIncluded) { + // get filter to put on the collection for reuse with refresh + String manyPropertyName = sqlTree.getManyPropertyName(); + OrmQueryProperties chunk = query.getDetail().getChunk(manyPropertyName, false); + this.filterMany = chunk.getFilterMany(); + } else { + this.filterMany = null; + } + + this.sql = queryPlan.getSql(); + this.rawSql = queryPlan.isRawSql(); + this.rowNumberIncluded = queryPlan.isRowNumberIncluded(); + this.logWhereSql = queryPlan.getLogWhereSql(); + this.desc = request.getBeanDescriptor(); + this.predicates = predicates; + + this.queryListener = query.getListener(); + if (queryListener == null) { + // normal, use the one from the transaction + this.persistenceContext = request.getPersistenceContext(); + } else { + // 'Row Level Transaction Context'... + // local transaction context that will be reset + // after each 'master' bean is sent to the listener + this.persistenceContext = new DefaultPersistenceContext(); + } + + this.maxRowsLimit = query.getMaxRows() > 0 ? query.getMaxRows() : GLOBAL_ROW_LIMIT; + this.backgroundFetchAfter = query.getBackgroundFetchAfter() > 0 ? query.getBackgroundFetchAfter() : Integer.MAX_VALUE; + + this.help = createHelp(request); + this.collection = (BeanCollection)(help != null ? help.createEmpty(false) : null); + } + + private BeanCollectionHelp createHelp(OrmQueryRequest request) { + if (request.isFindById()) { + return null; + } else { + SpiQuery.Type manyType = request.getQuery().getType(); + if (manyType == null){ + // subQuery compiled for InQueryExpression + return null; + } + return BeanCollectionHelpFactory.create(request); + } + } + + public Boolean isReadOnly() { + return readOnly; + } + + public void propagateState(Object e) { + if (Boolean.TRUE.equals(readOnly)){ + if (e instanceof EntityBean){ + ((EntityBean)e)._ebean_getIntercept().setReadOnly(true); + } + } + } + + public DataReader getDataReader() { + return dataReader; + } + + public Mode getQueryMode() { + return queryMode; + } + + /** + * Return true if we want to return vanilla (not enhanced) objects. + */ + public boolean isVanillaMode() { + return request.isVanillaMode(); + } + + public CQueryPredicates getPredicates() { + return predicates; + } + + public LoadContext getGraphContext() { + return request.getGraphContext(); + } + + public SpiOrmQueryRequest getQueryRequest() { + return request; + } + + public void cancel() { + synchronized (this) { + this.cancelled = true; + if (pstmt != null){ + try { + pstmt.cancel(); + } catch (SQLException e){ + String msg = "Error cancelling query"; + throw new PersistenceException(msg, e); + } + } + } + } + + public boolean prepareBindExecuteQuery() throws SQLException { + + synchronized (this) { + if (cancelled || query.isCancelled()){ + // cancelled before we started + cancelled = true; + return false; + } + + startNano = System.nanoTime(); + + // prepare + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + pstmt = conn.prepareStatement(sql); + + if (query.getTimeout() > 0){ + pstmt.setQueryTimeout(query.getTimeout()); + } + if (query.getBufferFetchSizeHint() > 0){ + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + + DataBind dataBind = new DataBind(pstmt); + + // bind keys for encrypted properties + queryPlan.bindEncryptedProperties(dataBind); + + bindLog = predicates.bind(dataBind); + + // executeQuery + ResultSet rset = pstmt.executeQuery(); + dataReader = queryPlan.createDataReader(rset); + + return true; + } + } + + /** + * Close the resources. + *

    + * The jdbc resultSet and statement need to be closed. Its important that + * this method is called. + *

    + */ + public void close() { + try { + if (dataReader != null) { + dataReader.close(); + dataReader = null; + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + +// /** +// * Return the reference options used to define cache use. +// */ +// public ReferenceOptions getReferenceOptionsFor(BeanPropertyAssocOne beanProp) { +// +// String beanPropName = beanProp.getName(); +// if (currentPrefix != null){ +// beanPropName = currentPrefix+"."+beanPropName; +// } +// //ReferenceOptions opt = referenceOptionsMap.get(beanPropName); +// if (opt == null){ +// OrmQueryProperties chunk = queryDetail.getChunk(beanPropName, false); +// if (chunk != null) { +// // get the options from the query +// opt = chunk.getReferenceOptions(); +// } +// if (opt == null){ +// // get the default options defined for the target bean type +// opt = beanProp.getTargetDescriptor().getReferenceOptions(); +// } +// referenceOptionsMap.put(beanPropName, opt); +// } +// +// return opt; +// } + + /** + * Return the persistence context. + */ + public PersistenceContext getPersistenceContext(){ + return persistenceContext; + } + + public void setLoadedBean(Object bean, Object id) { + if (id != null && id.equals(loadedBeanId)) { + // master/detail loading with master bean + // unchanged. NB Using id to avoid any issue + // with equals not being implemented + + } else { + if (manyIncluded) { + if (rowCount > 1) { + loadedBeanChanged = true; + } + this.prevLoadedBean = loadedBean; + this.loadedBeanId = id; + } + this.loadedBean = bean; + } + } + + public void setLoadedManyBean(Object manyValue) { + this.loadedManyBean = manyValue; + } + + /** + * Return the last read bean. + */ + @SuppressWarnings("unchecked") + public T getLoadedBean() { + if (manyIncluded) { + if (prevDetailCollection instanceof BeanCollection) { + ((BeanCollection)prevDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); + + } else if (currentDetailCollection instanceof BeanCollection) { + ((BeanCollection)currentDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); + } + } + + if (prevLoadedBean != null) { + return (T)prevLoadedBean; + } else { + return (T)loadedBean; + } + } + + private boolean hasMoreRows() throws SQLException { + synchronized (this) { + if (cancelled){ + return false; + } + return dataReader.next(); + } + } + + /** + * Read a row from the result set returning a bean. + *

    + * If the query includes a many then the first object in the returned array + * is the one/master and the second the many/detail. + *

    + */ + private boolean readRow() throws SQLException { + + synchronized (this) { + if (cancelled){ + return false; + } + + if (!dataReader.next()){ + return false; + } + + rowCount++; + dataReader.resetColumnPosition(); + + if (rowNumberIncluded) { + // row_number() column used for limit features + dataReader.incrementPos(1); + } + + rootNode.load(this, null); + + return true; + } + } + + public int getQueryExecutionTimeMicros(){ + return executionTimeMicros; + } + + public boolean readBean() throws SQLException { + + boolean result = readBeanInternal(true); + + updateExecutionStatistics(); + + return result; + } + + private boolean readBeanInternal(boolean inForeground) throws SQLException { + + if (loadedBeanCount >= maxRowsLimit) { + collection.setHasMoreRows(hasMoreRows()); + return false; + } + + if (inForeground && loadedBeanCount >= backgroundFetchAfter) { + hasHitBackgroundFetchAfter = true; + collection.setFinishedFetch(false); + return false; + } + + if (!manyIncluded) { + // simple query... no details... + return readRow(); + } + + if (noMoreRows) { + return false; + } + + if (rowCount == 0) { + if (!readRow()) { + // no rows at all... + return false; + } else { + createNewDetailCollection(); + } + } + + if (readIntoCurrentDetailCollection()) { + createNewDetailCollection(); + // return prevLoadedBean + return true; + + } else { + // return loadedBean + prevDetailCollection = null; + prevLoadedBean = null; + noMoreRows = true; + return true; + } + } + + private boolean readIntoCurrentDetailCollection() throws SQLException { + while (readRow()) { + if (loadedBeanChanged) { + loadedBeanChanged = false; + return true; + } else { + addToCurrentDetailCollection(); + } + } + return false; + } + + private BeanCollectionAdd currentDetailAdd; + + private void createNewDetailCollection() { + prevDetailCollection = currentDetailCollection; + if (queryMode.equals(Mode.LAZYLOAD_MANY)){ + // just populate the current collection + currentDetailCollection = manyPropertyEl.elGetValue(loadedBean); + } else { + // create a new collection to populate and assign to the bean + currentDetailCollection = manyProperty.createEmpty(request.isVanillaMode()); + manyPropertyEl.elSetValue(loadedBean, currentDetailCollection, false, false); + } + + if (filterMany != null && !request.isVanillaMode()){ + // remember the for use with a refresh + ((BeanCollection)currentDetailCollection).setFilterMany(filterMany); + } + + // the manyKey is always null for this case, just using default mapKey on the property + currentDetailAdd = manyProperty.getBeanCollectionAdd(currentDetailCollection, null); + addToCurrentDetailCollection(); + } + + private void addToCurrentDetailCollection() { + if (loadedManyBean != null) { + currentDetailAdd.addBean(loadedManyBean); + } + } + + public BeanCollection continueFetchingInBackground() throws SQLException { + readTheRows(false); + collection.setFinishedFetch(true); + return collection; + } + + public BeanCollection readCollection() throws SQLException { + + readTheRows(true); + + updateExecutionStatistics(); + + return collection; + } + + protected void updateExecutionStatistics() { + try { + long exeNano = System.nanoTime() - startNano; + executionTimeMicros = (int)exeNano/1000; + + if (autoFetchProfiling){ + autoFetchManager.collectQueryInfo(autoFetchParentNode, loadedBeanCount, executionTimeMicros); + } + queryPlan.executionTime(loadedBeanCount, executionTimeMicros); + + } catch (Exception e){ + logger.log(Level.SEVERE, null, e); + } + } + + public QueryIterator readIterate(int bufferSize, OrmQueryRequest request) { + + if (bufferSize > 0){ + return new CQueryIteratorWithBuffer(this, request, bufferSize); + + } else { + return new CQueryIteratorSimple(this, request); + } + } + + private void readTheRows(boolean inForeground) throws SQLException { + while (hasNextBean(inForeground)) { + if (queryListener != null) { + queryListener.process(getLoadedBean()); + + } else { + // add to the list/set/map + help.add(collection, getLoadedBean()); + } + } + } + + + protected boolean hasNextBean(boolean inForeground) throws SQLException { + + if (!readBeanInternal(inForeground)) { + return false; + + } else { + loadedBeanCount++; + return true; + } + } + + public String getLoadedRowDetail() { + if (!manyIncluded) { + return String.valueOf(rowCount); + } else { + return loadedBeanCount + ":" + rowCount; + } + } + + public void register(String path, EntityBeanIntercept ebi){ + + path = getPath(path); + request.getGraphContext().register(path, ebi); + } + + public void register(String path, BeanCollection bc){ + + path = getPath(path); + request.getGraphContext().register(path, bc); + } + + + public boolean useBackgroundToContinueFetch() { + return hasHitBackgroundFetchAfter; + } + + /** + * Return the query name. + */ + public String getName() { + return query.getName(); + } + + /** + * Return true if this is a raw sql query as opposed to Ebean generated sql. + */ + public boolean isRawSql() { + return rawSql; + } + + /** + * Return the where predicate for display in the transaction log. + */ + public String getLogWhereSql() { + return logWhereSql; + } + + /** + * Return the property that is associated with the many. There can only be + * one per SqlSelect. This can be null. + */ + public BeanPropertyAssocMany getManyProperty() { + return manyProperty; + } + + /** + * Get the summary of the sql. + */ + public String getSummary() { + return sqlTree.getSummary(); + } + + /** + * Return the SqlSelectChain. This is the flattened structure that + * represents this query. + */ + public SqlTree getSqlTree() { + return sqlTree; + } + + public String getBindLog() { + return bindLog; + } + + public SpiTransaction getTransaction() { + return request.getTransaction(); + } + + public String getBeanType() { + return desc.getFullName(); + } + + /** + * Return the short bean name. + */ + public String getBeanName() { + return desc.getName(); + } + + /** + * Return the generated sql. + */ + public String getGeneratedSql() { + return sql; + } + + /** + * Create a PersistenceException including interesting information like the bindLog and sql used. + */ + public PersistenceException createPersistenceException(SQLException e) { + + return createPersistenceException(e, getTransaction(), bindLog, sql); + } + + /** + * Create a PersistenceException including interesting information like the bindLog and sql used. + */ + public static PersistenceException createPersistenceException(SQLException e, SpiTransaction t, String bindLog, String sql) { + + if (t.isLogSummary()) { + // log the error to the transaction log + String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[] { "\r", "\n" }, "\\n "); + String msg = "ERROR executing query: bindLog[" + bindLog + "] error[" + errMsg + "]"; + t.logInternal(msg); + } + + // ensure 'rollback' is logged if queryOnly transaction + t.getConnection(); + + // build a decent error message for the exception + String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql); + return new PersistenceException(m, e); + } + + /** + * Should we create profileNodes for beans created in this query. + *

    + * This is true for all queries except lazy load bean queries. + *

    + */ + public boolean isAutoFetchProfiling() { + // need query.isProfiling() because we just take the data + // from the lazy loaded or refreshed beans and put it into the already + // existing beans which are already collecting usage information + return autoFetchProfiling && query.isUsageProfiling(); + } + + private String getPath(String propertyName) { + + if (currentPrefix == null){ + return propertyName; + } else if (propertyName == null) { + return currentPrefix; + } + + String path = currentPathMap.get(propertyName); + if (path != null){ + return path; + } else { + return currentPrefix+"."+propertyName; + } + } + + + public void profileBean(EntityBeanIntercept ebi, String prefix) { + + ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix); + + ebi.setNodeUsageCollector(new NodeUsageCollector(node, autoFetchManagerRef)); + } + + public void setCurrentPrefix(String currentPrefix, Map currentPathMap) { + this.currentPrefix = currentPrefix; + this.currentPathMap = currentPathMap; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java new file mode 100644 index 000000000..54c13d2cb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -0,0 +1,417 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.Iterator; +import java.util.Set; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.RawSql; +import com.avaje.ebean.RawSql.ColumnMapping; +import com.avaje.ebean.RawSql.ColumnMapping.Column; +import com.avaje.ebean.RawSqlBuilder; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.SqlLimitRequest; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebean.config.dbplatform.SqlLimiter; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; + +/** + * Generates the SQL SELECT statements taking into account the physical + * deployment properties. + */ +public class CQueryBuilder implements Constants { + + private final String tableAliasPlaceHolder; + private final String columnAliasPrefix; + + private final SqlLimiter sqlLimiter; + + private final RawSqlSelectClauseBuilder sqlSelectBuilder; + private final CQueryBuilderRawSql rawSqlHandler; + + private final Binder binder; + + private final BackgroundExecutor backgroundExecutor; + + private final boolean selectCountWithAlias; + + private DatabasePlatform dbPlatform; + + /** + * Create the SqlGenSelect. + */ + public CQueryBuilder(BackgroundExecutor backgroundExecutor, DatabasePlatform dbPlatform, Binder binder) { + + this.backgroundExecutor = backgroundExecutor; + this.binder = binder; + this.tableAliasPlaceHolder = GlobalProperties.get("ebean.tableAliasPlaceHolder", "${ta}"); + this.columnAliasPrefix = GlobalProperties.get("ebean.columnAliasPrefix", "c"); + this.sqlSelectBuilder = new RawSqlSelectClauseBuilder(dbPlatform, binder); + + this.sqlLimiter = dbPlatform.getSqlLimiter(); + this.rawSqlHandler = new CQueryBuilderRawSql(sqlLimiter, dbPlatform); + + this.selectCountWithAlias = dbPlatform.isSelectCountWithAlias(); + + this.dbPlatform = dbPlatform; + } + + /** + * split the order by claus on the field delimiter and prefix each field with + * the relation name + */ + public static String prefixOrderByFields(String name, String orderBy) { + StringBuilder sb = new StringBuilder(); + for (String token : orderBy.split(",")) { + if (sb.length() > 0) { + sb.append(", "); + } + + sb.append(name); + sb.append("."); + sb.append(token.trim()); + } + + return sb.toString(); + } + + /** + * Build the row count query. + */ + public CQueryFetchIds buildFetchIdsQuery(OrmQueryRequest request) { + + SpiQuery query = request.getQuery(); + + query.setSelectId(); + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + CQueryPlan queryPlan = request.getQueryPlan(); + if (queryPlan != null) { + // skip building the SqlTree and Sql string + predicates.prepare(false); + String sql = queryPlan.getSql(); + return new CQueryFetchIds(request, predicates, sql, backgroundExecutor); + + } + + // use RawSql or generated Sql + predicates.prepare(true); + + SqlTree sqlTree = createSqlTree(request, predicates); + SqlLimitResponse s = buildSql(null, request, predicates, sqlTree); + String sql = s.getSql(); + + // cache the query plan + queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); + + request.putQueryPlan(queryPlan); + return new CQueryFetchIds(request, predicates, sql, backgroundExecutor); + } + + /** + * Build the row count query. + */ + public CQueryRowCount buildRowCountQuery(OrmQueryRequest request) { + + SpiQuery query = request.getQuery(); + + // always set the order by to null for row count query + query.setOrder(null); + + boolean hasMany = !query.getManyWhereJoins().isEmpty(); + + query.setSelectId(); + + String sqlSelect = "select count(*)"; + if (hasMany) { + // need to count distinct id's ... + query.setDistinct(true); + sqlSelect = null; + } + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + CQueryPlan queryPlan = request.getQueryPlan(); + if (queryPlan != null) { + // skip building the SqlTree and Sql string + predicates.prepare(false); + String sql = queryPlan.getSql(); + return new CQueryRowCount(request, predicates, sql); + } + + predicates.prepare(true); + + SqlTree sqlTree = createSqlTree(request, predicates); + SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree); + String sql = s.getSql(); + if (hasMany) { + sql = "select count(*) from ( " + sql + ")"; + if (selectCountWithAlias) { + sql += " as c"; + } + } + + // cache the query plan + queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); + request.putQueryPlan(queryPlan); + + return new CQueryRowCount(request, predicates, sql); + } + + /** + * Return the SQL Select statement as a String. Converts logical property + * names to physical deployment column names. + */ + public CQuery buildQuery(OrmQueryRequest request) { + + if (request.isSqlSelect()) { + return sqlSelectBuilder.build(request); + } + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + + CQueryPlan queryPlan = request.getQueryPlan(); + if (queryPlan != null) { + // Reuse the query plan so skip generating SqlTree and SQL. + // We do prepare and bind the new parameters + predicates.prepare(false); + return new CQuery(request, predicates, queryPlan); + } + + // RawSql or Generated Sql query + + // Prepare the where, having and order by clauses. + // This also parses them from logical property names to + // database columns and determines 'includes'. + + // We need to check these 'includes' for extra joins + // that are not included via select + predicates.prepare(true); + + // Build the tree structure that represents the query. + SqlTree sqlTree = createSqlTree(request, predicates); + SqlLimitResponse res = buildSql(null, request, predicates, sqlTree); + + boolean rawSql = request.isRawSql(); + if (rawSql) { + queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql()); + + } else { + queryPlan = new CQueryPlan(request, res, sqlTree, rawSql, predicates.getLogWhereSql(), null); + } + + // cache the query plan because we can reuse it and also + // gather query performance statistics based on it. + request.putQueryPlan(queryPlan); + + return new CQuery(request, predicates, queryPlan); + } + + /** + * Build the SqlTree. + *

    + * The SqlTree is immutable after construction and so is safe to use by + * concurrent threads. + *

    + *

    + * The predicates is used to add additional joins that come from the where or + * order by clauses that are not already included for the select clause. + *

    + */ + private SqlTree createSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { + + if (request.isRawSql()) { + return createRawSqlSqlTree(request, predicates); + } + + return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates).build(); + } + + private SqlTree createRawSqlSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { + + BeanDescriptor descriptor = request.getBeanDescriptor(); + ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping(); + + PathProperties pathProps = new PathProperties(); + + // convert list of columns into (tree like) PathProperties + Iterator it = columnMapping.getColumns(); + while (it.hasNext()) { + RawSql.ColumnMapping.Column column = it.next(); + String propertyName = column.getPropertyName(); + if (!RawSqlBuilder.IGNORE_COLUMN.equals(propertyName)) { + + ElPropertyValue el = descriptor.getElGetValue(propertyName); + if (el == null) { + String msg = "Property [" + propertyName + "] not found on " + descriptor.getFullName(); + throw new PersistenceException(msg); + } + BeanProperty beanProperty = el.getBeanProperty(); + if (beanProperty.isId()) { + // For @Id properties we chop off the last part of the path + propertyName = SplitName.parent(propertyName); + } else if (beanProperty instanceof BeanPropertyAssocOne) { + String msg = "Column [" + column.getDbColumn() + "] mapped to complex Property[" + propertyName + "]"; + msg += ". It should be mapped to a simple property (proably the Id property). "; + throw new PersistenceException(msg); + } + if (propertyName != null) { + String[] pathProp = SplitName.split(propertyName); + pathProps.addToPath(pathProp[0], pathProp[1]); + } + } + } + + OrmQueryDetail detail = new OrmQueryDetail(); + + // transfer PathProperties into OrmQueryDetail + Iterator pathIt = pathProps.getPaths().iterator(); + while (pathIt.hasNext()) { + String path = pathIt.next(); + Set props = pathProps.get(path); + detail.getChunk(path, true).setDefaultProperties(null, props); + } + + // build SqlTree based on OrmQueryDetail of the RawSql + return new SqlTreeBuilder(request, predicates, detail).build(); + } + + private SqlLimitResponse buildSql(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select) { + + SpiQuery query = request.getQuery(); + + RawSql rawSql = query.getRawSql(); + if (rawSql != null) { + return rawSqlHandler.buildSql(request, predicates, rawSql.getSql()); + } + + BeanPropertyAssocMany manyProp = select.getManyProperty(); + + boolean useSqlLimiter = false; + + StringBuilder sb = new StringBuilder(500); + + if (selectClause != null) { + sb.append(selectClause); + + } else { + + useSqlLimiter = (query.hasMaxRowsOrFirstRow() && manyProp == null); + + if (!useSqlLimiter) { + sb.append("select "); + if (query.isDistinct()) { + sb.append("distinct "); + } + } + + sb.append(select.getSelectSql()); + } + + sb.append(" ").append(NEW_LINE); + sb.append("from "); + + // build the from clause potentially with joins + // required only for the predicates + sb.append(select.getFromSql()); + + String inheritanceWhere = select.getInheritanceWhereSql(); + + boolean hasWhere = false; + if (inheritanceWhere.length() > 0) { + sb.append(" ").append(NEW_LINE).append("where"); + sb.append(inheritanceWhere); + hasWhere = true; + } + + if (request.isFindById() || query.getId() != null) { + if (hasWhere) { + sb.append(" and "); + } else { + sb.append(NEW_LINE).append("where "); + } + + BeanDescriptor desc = request.getBeanDescriptor(); + String idSql = desc.getIdBinderIdSql(); + if (idSql.isEmpty()) { + throw new IllegalStateException("Executing FindById query on entity bean " + desc.getName() + + " that doesn't have an @Id property??"); + } + sb.append(idSql).append(" "); + hasWhere = true; + } + + String dbWhere = predicates.getDbWhere(); + if (!isEmpty(dbWhere)) { + if (!hasWhere) { + hasWhere = true; + sb.append(" ").append(NEW_LINE).append("where "); + } else { + sb.append("and "); + } + sb.append(dbWhere); + } + + String dbFilterMany = predicates.getDbFilterMany(); + if (!isEmpty(dbFilterMany)) { + if (!hasWhere) { + sb.append(" ").append(NEW_LINE).append("where "); + } else { + sb.append("and "); + } + sb.append(dbFilterMany); + } + + String dbOrderBy = predicates.getDbOrderBy(); + if (dbOrderBy != null) { + sb.append(" ").append(NEW_LINE); + sb.append("order by ").append(dbOrderBy); + } + + if (useSqlLimiter) { + // use LIMIT/OFFSET, ROW_NUMBER() or rownum type SQL query limitation + SqlLimitRequest r = new OrmQueryLimitRequest(sb.toString(), dbOrderBy, query, dbPlatform); + return sqlLimiter.limit(r); + + } else { + + return new SqlLimitResponse(dbPlatform.completeSql(sb.toString(), query), false); + } + + } + + private boolean isEmpty(String s) { + return s == null || s.length() == 0; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java new file mode 100644 index 000000000..303b2633f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java @@ -0,0 +1,167 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.RawSql; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebean.config.dbplatform.SqlLimiter; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; +import com.avaje.ebeaninternal.server.util.BindParamsParser; + +public class CQueryBuilderRawSql implements Constants { + + private final SqlLimiter sqlLimiter; + private final DatabasePlatform dbPlatform; + + CQueryBuilderRawSql(SqlLimiter sqlLimiter, DatabasePlatform dbPlatform) { + this.sqlLimiter = sqlLimiter; + this.dbPlatform = dbPlatform; + } + + /** + * Build the full SQL Select statement for the request. + */ + public SqlLimitResponse buildSql(OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql rsql) { + + if (!rsql.isParsed()){ + String sql = rsql.getUnparsedSql(); + BindParams bindParams = request.getQuery().getBindParams(); + if (bindParams != null && bindParams.requiresNamedParamsPrepare()){ + // convert named parameters into positioned parameters + sql = BindParamsParser.parse(bindParams, sql); + } + + return new SqlLimitResponse(sql, false); + } + + String orderBy = getOrderBy(predicates, rsql); + + // build the actual sql String + String sql = buildMainQuery(orderBy, request, predicates, rsql); + + SpiQuery query = request.getQuery(); + if (query.hasMaxRowsOrFirstRow() && sqlLimiter != null) { + // wrap with a limit offset or ROW_NUMBER() etc + return sqlLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform)); + + } else { + // add back select keyword (it was removed to support sqlQueryLimiter) + String prefix = "select "+ (rsql.isDistinct() ? "distinct " : ""); + sql = prefix + sql; + return new SqlLimitResponse(sql, false); + } + } + + private String buildMainQuery(String orderBy, OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql sql) { + + StringBuilder sb = new StringBuilder(); + sb.append(sql.getPreFrom()); + sb.append(" "); + sb.append(NEW_LINE); + + String s = sql.getPreWhere(); + BindParams bindParams = request.getQuery().getBindParams(); + if (bindParams != null && bindParams.requiresNamedParamsPrepare()){ + // convert named parameters into positioned parameters + // Named Parameters only allowed prior to dynamic where + // clause (so not allowed in having etc - use unparsed) + s = BindParamsParser.parse(bindParams, s); + } + sb.append(s); + sb.append(" "); + + String dynamicWhere = null; + if (request.getQuery().getId() != null) { + // need to convert this as well. This avoids the + // assumption that id has its proper dbColumn assigned + // which may change if using multiple raw sql statements + // against the same bean. + BeanDescriptor descriptor = request.getBeanDescriptor(); + //FIXME: I think this is broken... needs to be logical + // and then parsed for RawSqlSelect... + dynamicWhere = descriptor.getIdBinderIdSql(); + } + + String dbWhere = predicates.getDbWhere(); + if (!isEmpty(dbWhere)) { + if (dynamicWhere == null) { + dynamicWhere = dbWhere; + } else { + dynamicWhere += " and " + dbWhere; + } + } + + if (!isEmpty(dynamicWhere)) { + sb.append(NEW_LINE); + if (sql.isAndWhereExpr()) { + sb.append("and "); + } else { + sb.append("where "); + } + sb.append(dynamicWhere); + sb.append(" "); + } + + String preHaving = sql.getPreHaving(); + if (!isEmpty(preHaving)) { + sb.append(NEW_LINE); + sb.append(preHaving); + sb.append(" "); + } + + String dbHaving = predicates.getDbHaving(); + if (!isEmpty(dbHaving)) { + sb.append(" "); + sb.append(NEW_LINE); + if (sql.isAndHavingExpr()) { + sb.append("and "); + } else { + sb.append("having "); + } + sb.append(dbHaving); + sb.append(" "); + } + + if (!isEmpty(orderBy)) { + sb.append(NEW_LINE); + sb.append(" order by ").append(orderBy); + } + + return sb.toString().trim(); + } + + private boolean isEmpty(String s) { + return s == null || s.length() == 0; + } + + private String getOrderBy(CQueryPredicates predicates, RawSql.Sql sql) { + String orderBy = predicates.getDbOrderBy(); + if (orderBy != null) { + return orderBy; + } else { + return sql.getOrderBy(); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java new file mode 100644 index 000000000..55016c4c1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java @@ -0,0 +1,417 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.concurrent.FutureTask; +import java.util.logging.Logger; + +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionTouched; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.jmx.MAdminLogging; +import com.avaje.ebeaninternal.server.persist.Binder; + +/** + * Handles the Object Relational fetching. + */ +public class CQueryEngine { + + private static final Logger logger = Logger.getLogger(CQueryEngine.class.getName()); + + private final CQueryBuilder queryBuilder; + + private final MAdminLogging logControl; + + private final BackgroundExecutor backgroundExecutor; + + private final int defaultSecondaryQueryBatchSize = 100; + + public CQueryEngine(DatabasePlatform dbPlatform, MAdminLogging logControl, Binder binder, BackgroundExecutor backgroundExecutor) { + + this.logControl = logControl; + this.backgroundExecutor = backgroundExecutor; + this.queryBuilder = new CQueryBuilder(backgroundExecutor, dbPlatform, binder); + } + + public CQuery buildQuery(OrmQueryRequest request) { + return queryBuilder.buildQuery(request); + } + + /** + * Build and execute the find Id's query. + */ + public BeanIdList findIds(OrmQueryRequest request) { + + CQueryFetchIds rcQuery = queryBuilder.buildFetchIdsQuery(request); + try { + + String sql = rcQuery.getGeneratedSql(); + sql = sql.replace(Constants.NEW_LINE, ' '); + + if (logControl.isDebugGeneratedSql()) { + System.out.println(sql); + } + request.logSql(sql); + + BeanIdList list = rcQuery.findIds(); + + if (request.isLogSummary()) { + request.getTransaction().logInternal(rcQuery.getSummary()); + } + + if (!list.isFetchingInBackground() && request.getQuery().isFutureFetch()) { + // end the transaction for futureFindIds (it had it's own one) + logger.fine("Future findIds completed!"); + request.getTransaction().end(); + } + + return list; + + } catch (SQLException e) { + throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); + } + } + + /** + * Build and execute the row count query. + */ + public int findRowCount(OrmQueryRequest request) { + + CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request); + try { + + String sql = rcQuery.getGeneratedSql(); + sql = sql.replace(Constants.NEW_LINE, ' '); + + if (logControl.isDebugGeneratedSql()) { + System.out.println(sql); + } + request.logSql(sql); + + int rowCount = rcQuery.findRowCount(); + + if (request.isLogSummary()) { + request.getTransaction().logInternal(rcQuery.getSummary()); + } + + if (request.getQuery().isFutureFetch()) { + logger.fine("Future findRowCount completed!"); + request.getTransaction().end(); + } + + return rowCount; + + } catch (SQLException e) { + throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); + } + } + + /** + * Read many beans using an iterator (except you need to close() the iterator + * when you have finished). + */ + public QueryIterator findIterate(OrmQueryRequest request) { + + CQuery cquery = queryBuilder.buildQuery(request); + request.setCancelableQuery(cquery); + + try { + + if (logControl.isDebugGeneratedSql()) { + logSqlToConsole(cquery); + } + + if (request.isLogSql()) { + logSql(cquery); + } + + if (!cquery.prepareBindExecuteQuery()) { + // query has been cancelled already + logger.finest("Future fetch already cancelled"); + return null; + } + + int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize); + + QueryIterator readIterate = cquery.readIterate(iterateBufferSize, request); + + if (request.isLogSummary()) { + logFindManySummary(cquery); + } + + return readIterate; + + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + } + } + + /** + * Find a list/map/set of beans. + */ + public BeanCollection findMany(OrmQueryRequest request) { + + // flag indicating whether we need to close the resources... + boolean useBackgroundToContinueFetch = false; + + CQuery cquery = queryBuilder.buildQuery(request); + request.setCancelableQuery(cquery); + + try { + + if (logControl.isDebugGeneratedSql()) { + logSqlToConsole(cquery); + } + if (request.isLogSql()) { + logSql(cquery); + } + + if (!cquery.prepareBindExecuteQuery()) { + // query has been cancelled already + logger.finest("Future fetch already cancelled"); + return null; + } + + BeanCollection beanCollection = cquery.readCollection(); + + BeanCollectionTouched collectionTouched = request.getQuery().getBeanCollectionTouched(); + if (collectionTouched != null) { + // register a listener that wants to be notified when the + // bean collection is first used + beanCollection.setBeanCollectionTouched(collectionTouched); + } + + if (cquery.useBackgroundToContinueFetch()) { + // stop the request from putting connection back into pool + // before background fetching is finished. + request.setBackgroundFetching(); + useBackgroundToContinueFetch = true; + BackgroundFetch fetch = new BackgroundFetch(cquery); + + FutureTask future = new FutureTask(fetch); + beanCollection.setBackgroundFetch(future); + backgroundExecutor.execute(future); + } + + if (request.isLogSummary()) { + logFindManySummary(cquery); + } + + request.executeSecondaryQueries(defaultSecondaryQueryBatchSize); + + return beanCollection; + + } catch (SQLException e) { + throw cquery.createPersistenceException(e);// request, e, + // cquery.getBindLog(), + // cquery.getGeneratedSql()); + + } finally { + if (useBackgroundToContinueFetch) { + // left closing resources to BackgroundFetch... + } else { + if (cquery != null) { + cquery.close(); + } + if (request.getQuery().isFutureFetch()) { + // end the transaction for futureFindIds + // as it had it's own transaction + logger.fine("Future fetch completed!"); + request.getTransaction().end(); + } + } + } + } + + /** + * Find and return a single bean using its unique id. + */ + public T find(OrmQueryRequest request) { + + T bean = null; + + CQuery cquery = queryBuilder.buildQuery(request); + + try { + if (logControl.isDebugGeneratedSql()) { + logSqlToConsole(cquery); + } + if (request.isLogSql()) { + logSql(cquery); + } + + cquery.prepareBindExecuteQuery(); + + if (cquery.readBean()) { + bean = cquery.getLoadedBean(); + } + + if (request.isLogSummary()) { + logFindBeanSummary(cquery); + } + + request.executeSecondaryQueries(defaultSecondaryQueryBatchSize); + + return bean; + + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + + } finally { + cquery.close(); + } + } + + /** + * Log the generated SQL to the console. + */ + private void logSqlToConsole(CQuery cquery) { + + SpiQuery query = cquery.getQueryRequest().getQuery(); + String loadMode = query.getLoadMode(); + String loadDesc = query.getLoadDescription(); + + String sql = cquery.getGeneratedSql(); + String summary = cquery.getSummary(); + + StringBuilder sb = new StringBuilder(1000); + sb.append(""); + sb.append(Constants.NEW_LINE); + sb.append(sql); + sb.append(Constants.NEW_LINE).append(""); + + System.out.println(sb.toString()); + } + + /** + * Log the generated SQL to the transaction log. + */ + private void logSql(CQuery query) { + + String sql = query.getGeneratedSql(); + sql = sql.replace(Constants.NEW_LINE, ' '); + query.getTransaction().logInternal(sql); + } + + /** + * Log the FindById summary to the transaction log. + */ + private void logFindBeanSummary(CQuery q) { + + SpiQuery query = q.getQueryRequest().getQuery(); + String loadMode = query.getLoadMode(); + String loadDesc = query.getLoadDescription(); + String lazyLoadProp = query.getLazyLoadProperty(); + ObjectGraphNode node = query.getParentNode(); + String originKey; + if (node == null || node.getOriginQueryPoint() == null) { + originKey = null; + } else { + originKey = node.getOriginQueryPoint().getKey(); + } + + StringBuilder msg = new StringBuilder(200); + msg.append("FindBean "); + if (loadMode != null) { + msg.append("mode[").append(loadMode).append("] "); + } + msg.append("type[").append(q.getBeanName()).append("] "); + if (query.isAutofetchTuned()) { + msg.append("tuned[true] "); + } + if (originKey != null) { + msg.append("origin[").append(originKey).append("] "); + } + if (lazyLoadProp != null) { + msg.append("lazyLoadProp[").append(lazyLoadProp).append("] "); + } + if (loadDesc != null) { + msg.append("load[").append(loadDesc).append("] "); + } + msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros()); + msg.append("] rows[").append(q.getLoadedRowDetail()); + msg.append("] bind[").append(q.getBindLog()).append("]"); + + q.getTransaction().logInternal(msg.toString()); + } + + /** + * Log the FindMany to the transaction log. + */ + private void logFindManySummary(CQuery q) { + + SpiQuery query = q.getQueryRequest().getQuery(); + String loadMode = query.getLoadMode(); + String loadDesc = query.getLoadDescription(); + String lazyLoadProp = query.getLazyLoadProperty(); + ObjectGraphNode node = query.getParentNode(); + + String originKey; + if (node == null || node.getOriginQueryPoint() == null) { + originKey = null; + } else { + originKey = node.getOriginQueryPoint().getKey(); + } + + StringBuilder msg = new StringBuilder(200); + msg.append("FindMany "); + if (loadMode != null) { + msg.append("mode[").append(loadMode).append("] "); + } + msg.append("type[").append(q.getBeanName()).append("] "); + if (query.isAutofetchTuned()) { + msg.append("tuned[true] "); + } + if (originKey != null) { + msg.append("origin[").append(originKey).append("] "); + } + if (lazyLoadProp != null) { + msg.append("lazyLoadProp[").append(lazyLoadProp).append("] "); + } + if (loadDesc != null) { + msg.append("load[").append(loadDesc).append("] "); + } + msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros()); + msg.append("] rows[").append(q.getLoadedRowDetail()); + msg.append("] name[").append(q.getName()); + msg.append("] predicates[").append(q.getLogWhereSql()); + msg.append("] bind[").append(q.getBindLog()).append("]"); + + q.getTransaction().logInternal(msg.toString()); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java new file mode 100644 index 000000000..fd4278d1d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java @@ -0,0 +1,335 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.FutureTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.DataReader; +import com.avaje.ebeaninternal.server.type.RsetDataReader; + +/** + * Executes the select row count query. + */ +public class CQueryFetchIds { + + private static final Logger logger = Logger.getLogger(CQueryFetchIds.class.getName()); + + /** + * The overall find request wrapper object. + */ + private final OrmQueryRequest request; + + private final BeanDescriptor desc; + + private final SpiQuery query; + + private final BackgroundExecutor backgroundExecutor; + + /** + * Where clause predicates. + */ + private final CQueryPredicates predicates; + + /** + * The final sql that is generated. + */ + private final String sql; + + private RsetDataReader dataReader; + + /** + * The statement used to create the resultSet. + */ + private PreparedStatement pstmt; + + private String bindLog; + + private long startNano; + + private int executionTimeMicros; + + private int rowCount; + + private final int maxRows; + private final int bgFetchAfter; + + /** + * Create the Sql select based on the request. + */ + public CQueryFetchIds(OrmQueryRequest request, CQueryPredicates predicates, + String sql, BackgroundExecutor backgroundExecutor) { + + this.backgroundExecutor = backgroundExecutor; + this.request = request; + this.query = request.getQuery(); + this.sql = sql; + this.maxRows = query.getMaxRows(); + this.bgFetchAfter = query.getBackgroundFetchAfter(); + + query.setGeneratedSql(sql); + + this.desc = request.getBeanDescriptor(); + this.predicates = predicates; + + } + + /** + * Return a summary description of this query. + */ + public String getSummary() { + StringBuilder sb = new StringBuilder(); + sb.append("FindIds exeMicros[").append(executionTimeMicros) + .append("] rows[").append(rowCount) + .append("] type[").append(desc.getName()) + .append("] predicates[").append(predicates.getLogWhereSql()) + .append("] bind[").append(bindLog).append("]"); + + return sb.toString(); + } + + /** + * Return the bind log. + */ + public String getBindLog() { + return bindLog; + } + + /** + * Return the generated sql. + */ + public String getGeneratedSql() { + return sql; + } + + public SpiOrmQueryRequest getQueryRequest() { + return request; + } + + /** + * Execute the query returning the row count. + */ + public BeanIdList findIds() throws SQLException { + + boolean useBackgroundToContinueFetch = false; + + startNano = System.nanoTime(); + + try { + // get the list that we are going to put the id's into. + // This was already set so that it is available to be + // read by other threads (it is a synchronised list) + List idList = query.getIdList(); + if (idList == null){ + // running in foreground thread (not FutureIds query) + idList = Collections.synchronizedList(new ArrayList()); + query.setIdList(idList); + } + + BeanIdList result = new BeanIdList(idList); + + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + pstmt = conn.prepareStatement(sql); + + if (query.getBufferFetchSizeHint() > 0){ + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + + if (query.getTimeout() > 0){ + pstmt.setQueryTimeout(query.getTimeout()); + } + + bindLog = predicates.bind(new DataBind(pstmt)); + + ResultSet rset = pstmt.executeQuery(); + dataReader = new RsetDataReader(rset); + + boolean hitMaxRows = false; + boolean hasMoreRows = false; + rowCount = 0; + + DbReadContext ctx = new DbContext(); + + while (rset.next()){ + Object idValue = desc.getIdBinder().read(ctx); + idList.add(idValue); + // reset back to 0 + dataReader.resetColumnPosition(); + rowCount++; + + if (maxRows > 0 && rowCount == maxRows) { + hitMaxRows = true; + hasMoreRows = rset.next(); + break; + + } else if (bgFetchAfter > 0 && rowCount >= bgFetchAfter) { + useBackgroundToContinueFetch = true; + break; + } + } + + if (hitMaxRows){ + result.setHasMore(hasMoreRows); + } + + if (useBackgroundToContinueFetch){ + // tell the request not to end the transaction + // as we leave that up to the BackgroundIdFetch + request.setBackgroundFetching(); + + // submit background future task + BackgroundIdFetch bgFetch = new BackgroundIdFetch(t, rset, pstmt, ctx, desc, result); + FutureTask future = new FutureTask(bgFetch); + backgroundExecutor.execute(future); + + // set on result so we can use the futureTask to wait + result.setBackgroundFetch(future); + } + + long exeNano = System.nanoTime() - startNano; + executionTimeMicros = (int)exeNano/1000; + + return result; + + } finally { + if (useBackgroundToContinueFetch) { + // left closing resources to BackgroundFetch... + } else { + close(); + } + } + } + + /** + * Close the resources. + *

    + * The jdbc resultSet and statement need to be closed. Its important that + * this method is called. + *

    + */ + private void close() { + try { + if (dataReader != null) { + dataReader.close(); + dataReader = null; + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + + + class DbContext implements DbReadContext { + + public void propagateState(Object e) { + throw new RuntimeException("Not Called"); + } + + public Mode getQueryMode() { + return Mode.NORMAL; + } + + public DataReader getDataReader() { + return dataReader; + } + + public boolean isVanillaMode() { + return false; + } + + public Boolean isReadOnly() { + return Boolean.FALSE; + } + + public boolean isRawSql() { + return false; + } + + public void register(String path, EntityBeanIntercept ebi){ + } + + public void register(String path, BeanCollection bc){ + } + + public BeanPropertyAssocMany getManyProperty() { + // always null + return null; + } + + public PersistenceContext getPersistenceContext() { + // always null + return null; + } + + public boolean isAutoFetchProfiling() { + return false; + } + + public void profileBean(EntityBeanIntercept ebi, String prefix) { + // no-op + } + + public void setCurrentPrefix(String currentPrefix,Map pathMap) { + // no-op + } + + public void setLoadedBean(Object loadedBean, Object id) { + // no-op + } + + public void setLoadedManyBean(Object loadedBean) { + // no-op + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java new file mode 100644 index 000000000..f34d8a6b2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +/** + * + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; + +/** + * QueryIterator that does not require a buffer for secondary queries. + * + * @author rbygrave + */ +class CQueryIteratorSimple implements QueryIterator { + + private final CQuery cquery; + private final OrmQueryRequest request; + + CQueryIteratorSimple(CQuery cquery, OrmQueryRequest request){ + this.cquery = cquery; + this.request = request; + } + + public boolean hasNext() { + try { + return cquery.hasNextBean(true); + } catch (SQLException e){ + throw cquery.createPersistenceException(e); + } + } + + public T next() { + return cquery.getLoadedBean(); + } + + public void close() { + cquery.updateExecutionStatistics(); + cquery.close(); + request.endTransIfRequired(); + } + + public void remove() { + throw new PersistenceException("Remove not allowed"); + } +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java new file mode 100644 index 000000000..4c68e368a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +/** + * + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.ArrayList; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; + +/** + * A QueryIterator that uses a buffer to execute secondary queries periodically. + * + * @author rbygrave + */ +class CQueryIteratorWithBuffer implements QueryIterator { + + private final CQuery cquery; + private final int bufferSize; + private final OrmQueryRequest request; + private final ArrayList buffer; + + private boolean moreToLoad = true; + + CQueryIteratorWithBuffer(CQuery cquery, OrmQueryRequest request, int bufferSize) { + this.cquery = cquery; + this.request = request; + this.bufferSize = bufferSize; + this.buffer = new ArrayList(bufferSize); + } + + public boolean hasNext() { + try { + if (buffer.isEmpty() && moreToLoad) { + // load buffer + int i = -1; + while (moreToLoad && ++i < bufferSize) { + if (cquery.hasNextBean(true)) { + buffer.add(cquery.getLoadedBean()); + } else { + moreToLoad = false; + } + } + // execute secondary queries + request.executeSecondaryQueries(bufferSize); + } + return !buffer.isEmpty(); + + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + } + } + + public T next() { + return buffer.remove(0); + } + + public void close() { + cquery.updateExecutionStatistics(); + cquery.close(); + request.endTransIfRequired(); + } + + public void remove() { + throw new PersistenceException("Remove not allowed"); + } +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java new file mode 100644 index 000000000..44881c4aa --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; + +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.OrderBy.Property; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Creates the order by expression clause. + */ +public class CQueryOrderBy { + + private final BeanDescriptor desc; + + private final SpiQuery query; + + /** + * Create the logical order by clause. + */ + public static String parse(BeanDescriptor desc, SpiQuery query) { + return new CQueryOrderBy(desc, query).parseInternal(); + } + + private CQueryOrderBy(BeanDescriptor desc, SpiQuery query) { + this.desc = desc; + this.query = query; + } + + private String parseInternal() { + + OrderBy orderBy = query.getOrderBy(); + if (orderBy == null){ + return null; + } + + StringBuilder sb = new StringBuilder(); + + List properties = orderBy.getProperties(); + if (properties.isEmpty()){ + // order by clause removed by filterMany() + return null; + } + for (int i = 0; i < properties.size(); i++) { + if (i > 0){ + sb.append(", "); + } + Property p = properties.get(i); + String expression = parseProperty(p); + sb.append(expression); + } + return sb.toString(); + } + + private String parseProperty(Property p) { + + String propName = p.getProperty(); + ElPropertyValue el = desc.getElGetValue(propName); + if (el == null){ + return p.toStringFormat(); + } + + BeanProperty beanProperty = el.getBeanProperty(); + if (beanProperty instanceof BeanPropertyAssoc){ + BeanPropertyAssoc ap = (BeanPropertyAssoc)beanProperty; + IdBinder idBinder = ap.getTargetDescriptor().getIdBinder(); + return idBinder.getOrderBy(el.getElName(), p.isAscending()); + } + + return p.toStringFormat(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java new file mode 100644 index 000000000..161e94cef --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java @@ -0,0 +1,172 @@ +package com.avaje.ebeaninternal.server.query; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebean.meta.MetaQueryStatistic; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.DataReader; +import com.avaje.ebeaninternal.server.type.RsetDataReader; + +/** + * Represents a query for a given SQL statement. + *

    + * This can be executed multiple times with different bind parameters. + *

    + *

    + * That is, the sql including the where clause, order by clause etc must be + * exactly the same to share the same query plan with the only difference being + * bind values. + *

    + *

    + * This is useful in that is common in OLTP type applications that the same + * query will be executed quite a lot just with different bind values. With this + * query plan we can bypass some of the query statement generation (for + * performance) and collect statistics on the number and average execution + * times. This is turn can be used to identify queries that could be looked at + * for performance tuning. + *

    + */ +public class CQueryPlan { + + private final boolean autofetchTuned; + + private final int hash; + + private final boolean rawSql; + + private final boolean rowNumberIncluded; + + private final String sql; + + private final String logWhereSql; + + private final SqlTree sqlTree; + + /** + * Encrypted properties required additional binding. + */ + private final BeanProperty[] encryptedProps; + + private CQueryStats queryStats = new CQueryStats(); + + /** + * Create a query plan based on a OrmQueryRequest. + */ + public CQueryPlan(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, + boolean rawSql, String logWhereSql, String luceneQueryDescription) { + + this.hash = request.getQueryPlanHash(); + this.autofetchTuned = request.getQuery().isAutofetchTuned(); + if (sqlRes != null){ + this.sql = sqlRes.getSql(); + this.rowNumberIncluded = sqlRes.isIncludesRowNumberColumn(); + } else { + this.sql = luceneQueryDescription; + this.rowNumberIncluded = false; + } + this.sqlTree = sqlTree; + this.rawSql = rawSql; + this.logWhereSql = logWhereSql; + this.encryptedProps = sqlTree.getEncryptedProps(); + } + + /** + * Create a query plan for a raw sql query. + */ + public CQueryPlan(String sql, SqlTree sqlTree, + boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { + + this.hash = 0; + this.autofetchTuned = false; + this.sql = sql; + this.sqlTree = sqlTree; + this.rawSql = rawSql; + this.rowNumberIncluded = rowNumberIncluded; + this.logWhereSql = logWhereSql; + this.encryptedProps = sqlTree.getEncryptedProps(); + } + + public boolean isLucene() { + return false; + } + + public DataReader createDataReader(ResultSet rset){ + + return new RsetDataReader(rset); + } + + public void bindEncryptedProperties(DataBind dataBind) throws SQLException { + if (encryptedProps != null){ + for (int i = 0; i < encryptedProps.length; i++) { + String key = encryptedProps[i].getEncryptKey().getStringValue(); + dataBind.setString(key); + } + } + } + + public boolean isAutofetchTuned() { + return autofetchTuned; + } + + public int getHash() { + return hash; + } + + public String getSql() { + return sql; + } + + public SqlTree getSqlTree() { + return sqlTree; + } + + public boolean isRawSql() { + return rawSql; + } + + public boolean isRowNumberIncluded() { + return rowNumberIncluded; + } + + public String getLogWhereSql() { + return logWhereSql; + } + + /** + * Reset the query statistics. + */ + public void resetStatistics() { + queryStats = new CQueryStats(); + } + + /** + * Register an execution time against this query plan; + */ + public void executionTime(int loadedBeanCount, int timeMicros) { + // Atomic operation + queryStats = queryStats.add(loadedBeanCount, timeMicros); + } + + /** + * Return the current query statistics. + */ + public CQueryStats getQueryStats() { + return queryStats; + } + + /** + * Return the time this query plan was last used. + */ + public long getLastQueryTime(){ + return queryStats.getLastQueryTime(); + } + + public MetaQueryStatistic createMetaQueryStatistic(String beanName) { + return queryStats.createMetaQueryStatistic(beanName, this); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java new file mode 100644 index 000000000..a19e23d3a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.ResultSet; +import java.util.List; + +import com.avaje.ebean.RawSql.ColumnMapping; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.type.DataReader; +import com.avaje.ebeaninternal.server.type.RsetDataReaderIndexed; + +public class CQueryPlanRawSql extends CQueryPlan { + + private final int[] rsetIndexPositions; + + public CQueryPlanRawSql(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) { + + super(request, sqlRes, sqlTree, true, logWhereSql, null); + + this.rsetIndexPositions = createIndexPositions(request, sqlTree); + } + + public DataReader createDataReader(ResultSet rset){ + + return new RsetDataReaderIndexed(rset, rsetIndexPositions, isRowNumberIncluded()); + } + + + private int[] createIndexPositions(OrmQueryRequest request, SqlTree sqlTree) { + + List chain = sqlTree.buildSelectExpressionChain(); + ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping(); + + int[] indexPositions = new int[chain.size()]; + + for (int i = 0; i < chain.size(); i++) { + String expr = chain.get(i); + int indexPos = 1 + columnMapping.getIndexPosition(expr); + indexPositions[i] = indexPos; + } + + return indexPositions; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java new file mode 100644 index 000000000..92e8930f0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -0,0 +1,561 @@ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.BindParams.OrderedList; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DeployParser; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; +import com.avaje.ebeaninternal.util.DefaultExpressionRequest; + +/** + * Compile Query Predicates. + *

    + * This includes the where and having expressions which can be made up of + * Strings with named parameters or Expression objects. + *

    + *

    + * This builds the appropriate bits of where and having clauses and binds the + * named parameters and expression values into the prepared statement. + *

    + */ +public class CQueryPredicates { + + private static final Logger logger = Logger.getLogger(CQueryPredicates.class.getName()); + + private final Binder binder; + + private final OrmQueryRequest request; + + private final SpiQuery query; + + private final Object idValue; + + /** + * Flag set if this is a SqlSelect type query. + */ + private boolean rawSql; + + /** + * Named bind parameters. + */ + private final BindParams bindParams; + + /** + * Named bind parameters for the having clause. + */ + private OrderedList havingNamedParams; + + /** + * Bind values from the where expressions. + */ + private ArrayList filterManyExprBindValues; + + /** + * SQL generated from the where expressions. + */ + private String filterManyExprSql; + + /** + * Bind values from the where expressions. + */ + private ArrayList whereExprBindValues; + + /** + * SQL generated from the where expressions. + */ + private String whereExprSql; + + /** + * SQL generated from where with named parameters. + */ + private String whereRawSql; + + /** + * Bind values for having expression. + */ + private ArrayList havingExprBindValues; + + /** + * SQL generated from the having expression. + */ + private String havingExprSql; + + /** + * SQL generated from having with named parameters. + */ + private String havingRawSql; + + private String dbHaving; + + /** + * logicalWhere with property names converted to db columns. + */ + private String dbWhere; + + /** + * Filter than can apply to a many fetch join. + */ + private String dbFilterMany; + + /** + * The order by clause. + */ + private String logicalOrderBy; + + private String dbOrderBy; + + /** + * Includes from where and order by clauses. + */ + private Set predicateIncludes; + + public CQueryPredicates(Binder binder, OrmQueryRequest request) { + this.binder = binder; + this.request = request; + this.query = request.getQuery(); + this.bindParams = query.getBindParams(); + this.idValue = query.getId(); + } + + public String bind(DataBind dataBind) throws SQLException { + + StringBuilder bindLog = new StringBuilder(); + + if (idValue != null) { + // this is a find by id type query... + request.getBeanDescriptor().bindId(dataBind, idValue); + bindLog.append(idValue); + } + + if (bindParams != null) { + // bind named and positioned parameters... + binder.bind(bindParams, dataBind, bindLog); + } + + if (whereExprBindValues != null) { + + for (int i = 0; i < whereExprBindValues.size(); i++) { + Object bindValue = whereExprBindValues.get(i); + binder.bindObject(dataBind, bindValue); + if (i > 0 || idValue != null) { + bindLog.append(", "); + } + bindLog.append(bindValue); + } + } + + if (filterManyExprBindValues != null) { + + for (int i = 0; i < filterManyExprBindValues.size(); i++) { + Object bindValue = filterManyExprBindValues.get(i); + binder.bindObject(dataBind, bindValue); + if (i > 0 || idValue != null) { + bindLog.append(", "); + } + bindLog.append(bindValue); + } + } + + if (havingNamedParams != null) { + // bind named parameters in having... + bindLog.append(" havingNamed "); + binder.bind(havingNamedParams.list(), dataBind, bindLog); + } + + if (havingExprBindValues != null) { + // bind having expression... + bindLog.append(" having "); + for (int i = 0; i < havingExprBindValues.size(); i++) { + Object bindValue = havingExprBindValues.get(i); + binder.bindObject(dataBind, bindValue); + if (i > 0) { + bindLog.append(", "); + } + bindLog.append(bindValue); + } + } + + return bindLog.toString(); + } + + private void buildBindHavingRawSql(boolean buildSql, boolean parseRaw, DeployParser deployParser) { + if (buildSql || bindParams != null) { + // having clause with named parameters... + havingRawSql = query.getAdditionalHaving(); + if (parseRaw) { + havingRawSql = deployParser.parse(havingRawSql); + } + if (havingRawSql != null && bindParams != null) { + // convert and order named parameters if required + havingNamedParams = BindParamsParser.parseNamedParams(bindParams, havingRawSql); + havingRawSql = havingNamedParams.getPreparedSql(); + } + } + } + + /** + * Convert named parameters into an OrderedList. + */ + private void buildBindWhereRawSql(boolean buildSql, boolean parseRaw, DeployParser parser) { + if (buildSql || bindParams != null) { + whereRawSql = buildWhereRawSql(); + boolean hasRaw = !"".equals(whereRawSql); + if (hasRaw && parseRaw) { + // parse with encrypted property awareness. This means that if we have + // an + // encrypted property we will insert special named parameter place + // holders + // for binding the encryption key values + parser.setEncrypted(true); + whereRawSql = parser.parse(whereRawSql); + parser.setEncrypted(false); + } + + if (bindParams != null) { + if (hasRaw) { + whereRawSql = BindParamsParser.parse(bindParams, whereRawSql, request.getBeanDescriptor()); + + } else if (query.isRawSql() && !buildSql) { + // RawSql query hit cached query plan. Need to convert + // named parameters into positioned parameters so that + // the named parameters are bound + String s = query.getRawSql().getSql().getPreWhere(); + if (bindParams.requiresNamedParamsPrepare()) { + BindParamsParser.parse(bindParams, s); + } + } + } + } + } + + private String buildWhereRawSql() { + // this is the where part of a OQL query which + // may contain bind parameters... + String whereRaw = query.getRawWhereClause(); + if (whereRaw == null) { + whereRaw = ""; + } + // add any additional stuff to the where clause + String additionalWhere = query.getAdditionalWhere(); + if (additionalWhere != null) { + whereRaw += additionalWhere; + } + return whereRaw; + } + + public void prepare(boolean buildSql) { + + DeployParser deployParser = request.createDeployParser(); + + prepare(buildSql, true, deployParser); + } + + public void prepareRawSql(DeployParser deployParser) { + prepare(true, false, deployParser); + } + + /** + * This combines the sql from named/positioned parameters and expressions. + */ + private void prepare(boolean buildSql, boolean parseRaw, DeployParser deployParser) { + + buildBindWhereRawSql(buildSql, parseRaw, deployParser); + buildBindHavingRawSql(buildSql, parseRaw, deployParser); + + SpiExpressionList whereExp = query.getWhereExpressions(); + if (whereExp != null) { + DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser); + whereExprBindValues = whereExp.buildBindValues(whereReq); + if (buildSql) { + whereExprSql = whereExp.buildSql(whereReq); + } + } + + BeanPropertyAssocMany manyProperty = request.getManyProperty(); + if (manyProperty != null) { + OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false); + SpiExpressionList filterMany = chunk.getFilterMany(); + if (filterMany != null) { + DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser); + filterManyExprBindValues = filterMany.buildBindValues(filterReq); + if (buildSql) { + filterManyExprSql = filterMany.buildSql(filterReq); + } + } + } + + // having expression + SpiExpressionList havingExpr = query.getHavingExpressions(); + if (havingExpr != null) { + DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser); + havingExprBindValues = havingExpr.buildBindValues(havingReq); + if (buildSql) { + havingExprSql = havingExpr.buildSql(havingReq); + } + } + + if (buildSql) { + parsePropertiesToDbColumns(deployParser); + } + + } + + /** + * Parse/Convert property names to database columns in the where and order by + * clauses etc. + */ + private void parsePropertiesToDbColumns(DeployParser deployParser) { + + dbWhere = deriveWhere(deployParser); + dbFilterMany = deriveFilterMany(deployParser); + dbHaving = deriveHaving(deployParser); + + // order by is dependent on the manyProperty (if there is one) + logicalOrderBy = deriveOrderByWithMany(request.getManyProperty()); + if (logicalOrderBy != null) { + dbOrderBy = deployParser.parse(logicalOrderBy); + } + + predicateIncludes = deployParser.getIncludes(); + } + + private String deriveFilterMany(DeployParser deployParser) { + if (isEmpty(filterManyExprSql)) { + return null; + } else { + return deployParser.parse(filterManyExprSql); + } + } + + private String deriveWhere(DeployParser deployParser) { + return parse(whereRawSql, whereExprSql, deployParser); + } + + /** + * Replace the table alias place holders. + */ + public void parseTableAlias(SqlTreeAlias alias) { + if (dbWhere != null) { + // use the where table alias + dbWhere = alias.parseWhere(dbWhere); + } + if (dbFilterMany != null) { + // use the select table alias + dbFilterMany = alias.parse(dbFilterMany); + } + if (dbHaving != null) { + dbHaving = alias.parseWhere(dbHaving); + } + if (dbOrderBy != null) { + dbOrderBy = alias.parse(dbOrderBy); + } + } + + private boolean isEmpty(String s) { + return s == null || s.length() == 0; + } + + private String parse(String raw, String expr, DeployParser deployParser) { + + StringBuilder sb = new StringBuilder(); + if (!isEmpty(raw)) { + sb.append(raw); + } + if (!isEmpty(expr)) { + if (sb.length() > 0) { + sb.append(" and "); + } + sb.append(deployParser.parse(expr)); + } + return sb.toString(); + } + + private String deriveHaving(DeployParser deployParser) { + return parse(havingRawSql, havingExprSql, deployParser); + } + + private String parseOrderBy() { + + return CQueryOrderBy.parse(request.getBeanDescriptor(), query); + } + + /** + * There is a many property so we need to make sure the ordering is + * appropriate. + */ + private String deriveOrderByWithMany(BeanPropertyAssocMany manyProp) { + + if (manyProp == null) { + return parseOrderBy(); + } + + String orderBy = parseOrderBy(); + + BeanDescriptor desc = request.getBeanDescriptor(); + String orderById = desc.getDefaultOrderBy(); + + if (orderBy == null) { + orderBy = orderById; + } + + // check for default ordering on the many property... + String manyOrderBy = manyProp.getFetchOrderBy(); + if (manyOrderBy != null) { + orderBy = orderBy + ", " + CQueryBuilder.prefixOrderByFields(manyProp.getName(), manyOrderBy); + } + + if (request.isFindById()) { + // only one master bean so should be fine... + return orderBy; + } + + if (orderBy.startsWith(orderById)) { + return orderBy; + } + + // more than one top level row may be returned so + // we need to make sure their is an order by on the + // top level first (to ensure master/detail construction). + + int manyPos = orderBy.indexOf(manyProp.getName()); + int idPos = orderBy.indexOf(" " + orderById); + + if (manyPos == -1) { + // no ordering of the many + if (idPos == -1) { + // append the orderById so that master level objects are ordered + // even if the orderBy is not unique for the master object + return orderBy + ", " + orderById; + } + // orderById is already in the order by clause + return orderBy; + } + + if (idPos > -1 && idPos < manyPos) { + // its all ok, id property appears before a many property + + } else { + if (idPos > manyPos) { + // there was an error with the order by... + String msg = "A Query on [" + desc + "] includes a join to a 'many' association [" + manyProp.getName(); + msg += "] with an incorrect orderBy [" + orderBy + "]. The id property [" + orderById + "]"; + msg += " must come before the many property [" + manyProp.getName() + "] in the orderBy."; + msg += " Ebean has automatically modified the orderBy clause to do this."; + + logger.log(Level.WARNING, msg); + } + + // the id needs to come before the manyPropName + orderBy = orderBy.substring(0, manyPos) + orderById + ", " + orderBy.substring(manyPos); + } + + return orderBy; + } + + /** + * Return the bind values for the where expression. + */ + public ArrayList getWhereExprBindValues() { + return whereExprBindValues; + } + + /** + * Return the db column version of the combined where clause. + */ + public String getDbHaving() { + return dbHaving; + } + + /** + * Return the db column version of the combined where clause. + */ + public String getDbWhere() { + return dbWhere; + } + + /** + * Return a db filter for filtering many fetch joins. + */ + public String getDbFilterMany() { + return dbFilterMany; + } + + /** + * Return the db column version of the order by clause. + */ + public String getDbOrderBy() { + return dbOrderBy; + } + + /** + * Return the includes required for the where and order by clause. + */ + public Set getPredicateIncludes() { + return predicateIncludes; + } + + /** + * The where sql with named bind parameters converted to ?. + */ + public String getWhereRawSql() { + return whereRawSql; + } + + /** + * The where sql from the expression objects. + */ + public String getWhereExpressionSql() { + return whereExprSql; + } + + /** + * The having sql with named bind parameters converted to ?. + */ + public String getHavingRawSql() { + return havingRawSql; + } + + /** + * The having sql from the expression objects. + */ + public String getHavingExpressionSql() { + return havingExprSql; + } + + public String getLogWhereSql() { + if (rawSql) { + return ""; + } + if (dbWhere == null && dbFilterMany == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + if (dbWhere != null) { + sb.append(dbWhere); + } + if (dbFilterMany != null) { + if (sb.length() > 0) { + sb.append(" and "); + } + sb.append(dbFilterMany); + } + String logPred = sb.toString(); + if (logPred.length() > 400) { + logPred = logPred.substring(0, 400) + " ..."; + } + return logPred; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java new file mode 100644 index 000000000..25e438039 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java @@ -0,0 +1,192 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Executes the select row count query. + */ +public class CQueryRowCount { + + private static final Logger logger = Logger.getLogger(CQueryRowCount.class.getName()); + + /** + * The overall find request wrapper object. + */ + private final OrmQueryRequest request; + + private final BeanDescriptor desc; + + private final SpiQuery query; + + /** + * Where clause predicates. + */ + private final CQueryPredicates predicates; + + /** + * The final sql that is generated. + */ + private final String sql; + + /** + * The resultSet that is read and converted to objects. + */ + private ResultSet rset; + + /** + * The statement used to create the resultSet. + */ + private PreparedStatement pstmt; + + private String bindLog; + + private long startNano; + + private int executionTimeMicros; + + private int rowCount; + + /** + * Create the Sql select based on the request. + */ + public CQueryRowCount(OrmQueryRequest request, CQueryPredicates predicates, String sql) { + this.request = request; + this.query = request.getQuery(); + this.sql = sql; + + query.setGeneratedSql(sql); + + this.desc = request.getBeanDescriptor(); + this.predicates = predicates; + + } + + /** + * Return a summary description of this query. + */ + public String getSummary() { + StringBuilder sb = new StringBuilder(); + sb.append("FindRowCount exeMicros[").append(executionTimeMicros) + .append("] rows[").append(rowCount) + .append("] type[").append(desc.getFullName()) + .append("] predicates[").append(predicates.getLogWhereSql()) + .append("] bind[").append(bindLog).append("]"); + + return sb.toString(); + } + + /** + * Return the bind log. + */ + public String getBindLog() { + return bindLog; + } + + /** + * Return the generated sql. + */ + public String getGeneratedSql() { + return sql; + } + + public SpiOrmQueryRequest getQueryRequest() { + return request; + } + + /** + * Execute the query returning the row count. + */ + public int findRowCount() throws SQLException { + + startNano = System.nanoTime(); + try { + + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + pstmt = conn.prepareStatement(sql); + + if (query.getTimeout() > 0){ + pstmt.setQueryTimeout(query.getTimeout()); + } + + bindLog = predicates.bind(new DataBind(pstmt)); + + rset = pstmt.executeQuery(); + + if (!rset.next()){ + throw new PersistenceException("Expecting 1 row but got none?"); + } + + rowCount = rset.getInt(1); + + long exeNano = System.nanoTime() - startNano; + executionTimeMicros = (int)exeNano/1000; + + return rowCount; + + } finally { + close(); + } + } + + /** + * Close the resources. + *

    + * The jdbc resultSet and statement need to be closed. Its important that + * this method is called. + *

    + */ + private void close() { + try { + if (rset != null) { + rset.close(); + rset = null; + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryStats.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryStats.java new file mode 100644 index 000000000..616f62b5f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryStats.java @@ -0,0 +1,77 @@ + +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.meta.MetaQueryStatistic; + +/** + * Statistics for query plan that can accumulate. + */ +public final class CQueryStats { + + private final int count; + + private final int totalLoadedBeanCount; + + private final int totalTimeMicros; + + private final long startCollecting; + + private final long lastQueryTime; + + public CQueryStats() { + count = 0; + totalLoadedBeanCount = 0; + totalTimeMicros = 0; + startCollecting = System.currentTimeMillis(); + lastQueryTime = 0; + } + + /** + * Accumulate/Increment the statistics based on the previous statistics. + */ + public CQueryStats(CQueryStats previous, int loadedBeanCount, int timeMicros) { + count = previous.count + 1; + totalLoadedBeanCount = previous.totalLoadedBeanCount + loadedBeanCount; + totalTimeMicros = previous.totalTimeMicros + timeMicros; + startCollecting = previous.startCollecting; + lastQueryTime = System.currentTimeMillis(); + } + + public CQueryStats add(int loadedBeanCount, int timeMicros) { + return new CQueryStats(this, loadedBeanCount, timeMicros); + } + + public int getCount() { + return count; + } + + public int getAverageTimeMicros() { + if (count == 0) { + return 0; + } else { + return totalTimeMicros / count; + } + } + + public int getTotalLoadedBeanCount() { + return totalLoadedBeanCount; + } + + public int getTotalTimeMicros() { + return totalTimeMicros; + } + + public long getStartCollecting() { + return startCollecting; + } + + public long getLastQueryTime() { + return lastQueryTime; + } + + public MetaQueryStatistic createMetaQueryStatistic(String beanName, CQueryPlan qp) { + return new MetaQueryStatistic(qp.isAutofetchTuned(), beanName, qp.getHash(), + qp.getSql(), count, totalLoadedBeanCount, totalTimeMicros, startCollecting, lastQueryTime); + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java new file mode 100644 index 000000000..8a7bb5c02 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; + +/** + * Base object for making query execution into Callable's. + * + * @author rbygrave + * + * @param the entity bean type + */ +public abstract class CallableQuery { + + protected final Query query; + + protected final SpiEbeanServer server; + + protected final Transaction t; + + public CallableQuery(SpiEbeanServer server, Query query, Transaction t) { + this.server = server; + this.query = query; + this.t = t; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java new file mode 100644 index 000000000..49aa8d753 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Callable; + +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; + +/** + * Represent the fetch Id's query as a Callable. + * + * @param the entity bean type + */ +public class CallableQueryIds extends CallableQuery implements Callable> { + + + public CallableQueryIds(SpiEbeanServer server, Query query, Transaction t) { + super(server, query, t); + } + + /** + * Execute the find Id's query returning the list of Id's. + */ + public List call() throws Exception { + // we have already made a copy of the query + // this way the same query instance is available to the + // QueryFutureIds (as so has access to the List before it is done) + return server.findIdsWithCopy(query, t); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java new file mode 100644 index 000000000..b046d0e27 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Callable; + +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; + +/** + * Represent the findList query as a Callable. + * + * @param the entity bean type + */ +public class CallableQueryList extends CallableQuery implements Callable> { + + + public CallableQueryList(SpiEbeanServer server, Query query, Transaction t) { + super(server, query, t); + } + + /** + * Execute the query returning the resulting List. + */ + public List call() throws Exception { + return server.findList(query, t); + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java new file mode 100644 index 000000000..f660ad62e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.Callable; + +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; + +/** + * Represent the findRowCount query as a Callable. + * + * @param the entity bean type + */ +public class CallableQueryRowCount extends CallableQuery implements Callable { + + + public CallableQueryRowCount(SpiEbeanServer server, Query query, Transaction t) { + super(server, query, t); + } + + /** + * Execute the query returning the row count. + */ + public Integer call() throws Exception { + return server.findRowCountWithCopy(query, t); + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java new file mode 100644 index 000000000..c05c4db0e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Callable; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; +import com.avaje.ebean.Transaction; + +/** + * Represent the SQL query findList as a Callable. + */ +public class CallableSqlQueryList implements Callable> { + + private final SqlQuery query; + + private final EbeanServer server; + + private final Transaction t; + + public CallableSqlQueryList(EbeanServer server, SqlQuery query, Transaction t) { + this.server = server; + this.query = query; + this.t = t; + } + + /** + * Execute the query returning the resulting list. + */ + public List call() throws Exception { + return server.findList(query, t); + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java new file mode 100644 index 000000000..0ae7c1c1f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +/** + * Defines a cancelable query. + *

    + * Typically holds a representation of the PreparedStatement to perform the + * actual cancel. + *

    + */ +public interface CancelableQuery { + + /** + * Cancel the query. + *

    + * For JDBC this translates to calling cancel on the PreparedStatement. + *

    + */ + public void cancel(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/Constants.java b/src/main/java/com/avaje/ebeaninternal/server/query/Constants.java new file mode 100644 index 000000000..46684e0e3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/Constants.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +/** + * Constants used in find processing. + */ +public interface Constants { + + /** + * the new line character used. + *

    + * Note that this is removed for logging sql to the transaction log. + *

    + */ + public static final char NEW_LINE = '\n'; + + /** + * The carriage return character. + */ + public static final char CARRIAGE_RETURN = '\r'; + + /** + * literal used for SQL LIMIT in MySql and Postgres. + */ + public static final String LIMIT = "limit"; + + /** + * Literal used for SQL LIMIT OFFSET clause in MySql and Postgres. + */ + public static final String OFFSET = "offset"; + + /** + * ROW_NUMBER() OVER (ORDER BY + */ + public static final String ROW_NUMBER_OVER = "row_number() over (order by "; + + /** + * ) as rn, + */ + public static final String ROW_NUMBER_AS = ") as rn, "; +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java new file mode 100644 index 000000000..19f38b8bc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultDbSqlContext.java @@ -0,0 +1,274 @@ +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.HashSet; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.TableJoinColumn; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.util.ArrayStack; + +public class DefaultDbSqlContext implements DbSqlContext { + + private static final String NEW_LINE = "\n"; + + private static final String COMMA = ", "; + + private static final String PERIOD = "."; + + private final String tableAliasPlaceHolder; + + private final String columnAliasPrefix; + + private final ArrayStack tableAliasStack = new ArrayStack(); + + private final ArrayStack joinStack = new ArrayStack(); + + private final ArrayStack prefixStack = new ArrayStack(); + + private final boolean useColumnAlias; + + private int columnIndex; + + private StringBuilder sb = new StringBuilder(140); + + /** + * A Set used to make sure formula joins are only added once to a query. + */ + private HashSet formulaJoins; + + private HashSet tableJoins; + + private SqlTreeAlias alias; + + private String currentPrefix; + + private ArrayList encryptedProps; + + /** + * Construct for FROM clause (no column alias used). + */ + public DefaultDbSqlContext(SqlTreeAlias alias, String tableAliasPlaceHolder) { + this.tableAliasPlaceHolder = tableAliasPlaceHolder; + this.columnAliasPrefix = null; + this.useColumnAlias = false; + this.alias = alias; + } + + /** + * Construct for SELECT clause (with column alias settings). + */ + public DefaultDbSqlContext(SqlTreeAlias alias, String tableAliasPlaceHolder, String columnAliasPrefix, + boolean alwaysUseColumnAlias) { + this.alias = alias; + this.tableAliasPlaceHolder = tableAliasPlaceHolder; + this.columnAliasPrefix = columnAliasPrefix; + this.useColumnAlias = alwaysUseColumnAlias; + } + + public void addEncryptedProp(BeanProperty p) { + if (encryptedProps == null){ + encryptedProps = new ArrayList(); + } + encryptedProps.add(p); + } + + public BeanProperty[] getEncryptedProps() { + if (encryptedProps == null){ + return null; + } + + return encryptedProps.toArray(new BeanProperty[encryptedProps.size()]); + } + + public String peekJoin() { + return joinStack.peek(); + } + + public void popJoin() { + joinStack.pop(); + } + + public void pushJoin(String node) { + joinStack.push(node); + } + + public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2) { + + if (tableJoins == null) { + tableJoins = new HashSet(); + } + + String joinKey = table + "-" + a1 + "-" + a2; + if (tableJoins.contains(joinKey)) { + return; + } + + tableJoins.add(joinKey); + + sb.append(NEW_LINE); + sb.append(type); + + sb.append(" ").append(table).append(" "); + sb.append(a2); + + sb.append(" on "); + + for (int i = 0; i < cols.length; i++) { + TableJoinColumn pair = cols[i]; + if (i > 0) { + sb.append(" and "); + } + + sb.append(a2); + sb.append(".").append(pair.getForeignDbColumn()); + sb.append(" = "); + sb.append(a1); + sb.append(".").append(pair.getLocalDbColumn()); + } + + sb.append(" "); + } + + public String getTableAlias(String prefix) { + return alias.getTableAlias(prefix); + } + + public String getTableAliasManyWhere(String prefix) { + return alias.getTableAliasManyWhere(prefix); + } + + public void pushSecondaryTableAlias(String alias) { + tableAliasStack.push(alias); + } + + public String getRelativePrefix(String propName) { + + return currentPrefix == null ? propName : currentPrefix + "." + propName; + } + + public void pushTableAlias(String prefix) { + // store the currentPrefix on a stack + prefixStack.push(currentPrefix); + currentPrefix = prefix; + tableAliasStack.push(getTableAlias(prefix)); + } + + public void popTableAlias() { + tableAliasStack.pop(); + // pop the currentPrefix from the stack + currentPrefix = prefixStack.pop();; + } + + public StringBuilder getBuffer() { + return sb; + } + + public DefaultDbSqlContext append(String s) { + sb.append(s); + return this; + } + + public DefaultDbSqlContext append(char s) { + sb.append(s); + return this; + } + + public void appendFormulaJoin(String sqlFormulaJoin, boolean forceOuterJoin) { + + // replace ${ta} place holder with the real table alias... + String tableAlias = tableAliasStack.peek(); + String converted = StringHelper.replaceString(sqlFormulaJoin, tableAliasPlaceHolder, tableAlias); + + if (formulaJoins == null) { + formulaJoins = new HashSet(); + + } else if (formulaJoins.contains(converted)) { + // skip adding a formula join because + // the same join has already been added. + return; + } + + // we only want to add this join once + formulaJoins.add(converted); + + sb.append(NEW_LINE); + + if (forceOuterJoin) { + if ("join".equals(sqlFormulaJoin.substring(0, 4).toLowerCase())) { + // prepend left outer as we are in the 'many' part + append(" left outer "); + } + } + + sb.append(converted); + sb.append(" "); + } + + public void appendFormulaSelect(String sqlFormulaSelect) { + + String tableAlias = tableAliasStack.peek(); + String converted = StringHelper.replaceString(sqlFormulaSelect, tableAliasPlaceHolder, tableAlias); + + sb.append(COMMA); + sb.append(converted); + } + + public void appendColumn(String column) { + appendColumn(tableAliasStack.peek(), column); + } + + public void appendColumn(String tableAlias, String column) { + sb.append(COMMA); + + if (column.indexOf("${}") > -1){ + // support DB functions such as lower() etc + // with the use of secondary columns + String x = StringHelper.replaceString(column, "${}", tableAlias); + sb.append(x); + } else { + sb.append(tableAlias); + sb.append(PERIOD); + sb.append(column); + } + if (useColumnAlias) { + sb.append(" "); + sb.append(columnAliasPrefix); + sb.append(columnIndex); + } + columnIndex++; + } + + public String peekTableAlias() { + return tableAliasStack.peek(); + } + + public void appendRawColumn(String rawcolumnWithTableAlias) { + sb.append(COMMA); + sb.append(rawcolumnWithTableAlias); + + if (useColumnAlias) { + sb.append(" "); + sb.append(columnAliasPrefix); + sb.append(columnIndex); + } + columnIndex++; + } + + public int length() { + return sb.length(); + } + + public String getContent() { + String s = sb.toString(); + sb = new StringBuilder(); + return s; + } + + public String toString() { + return "DefaultDbSqlContext: "+sb.toString(); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java new file mode 100644 index 000000000..26aad5778 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java @@ -0,0 +1,148 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.Collection; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.OrmQueryEngine; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; + +/** + * Main Finder implementation. + */ +public class DefaultOrmQueryEngine implements OrmQueryEngine { + + /** + * Find using predicates + */ + private final CQueryEngine queryEngine; + + /** + * Create the Finder. + */ + public DefaultOrmQueryEngine(BeanDescriptorManager descMgr, CQueryEngine queryEngine) { + + this.queryEngine = queryEngine; + } + + public int findRowCount(OrmQueryRequest request){ + + return queryEngine.findRowCount(request); + } + + public BeanIdList findIds(OrmQueryRequest request){ + + return queryEngine.findIds(request); + } + + + public QueryIterator findIterate(OrmQueryRequest request) { + + // LIMITATION: You can not use QueryIterator to load bean cache + + SpiTransaction t = request.getTransaction(); + + // before we perform a query, we need to flush any + // previous persist requests that are queued/batched. + // The query may read data affected by those requests. + t.flushBatch(); + + return queryEngine.findIterate(request); + } + + public BeanCollection findMany(OrmQueryRequest request) { + + SpiQuery query = request.getQuery(); + + BeanCollection result = null; + + SpiTransaction t = request.getTransaction(); + + // before we perform a query, we need to flush any + // previous persist requests that are queued/batched. + // The query may read data affected by those requests. + t.flushBatch(); + + BeanFinder finder = request.getBeanFinder(); + if (finder != null) { + // this bean type has its own specific finder + result = finder.findMany(request); + } else { + result = queryEngine.findMany(request); + } + + if (query.isLoadBeanCache()){ + // load the individual beans into the bean cache + BeanDescriptor descriptor = request.getBeanDescriptor(); + Collection c = result.getActualDetails(); + for (T bean : c) { + descriptor.cachePutBeanData(bean); + } + } + + if (!result.isEmpty() && query.isUseQueryCache()){ + // load the query result into the query cache + request.putToQueryCache(result); + } + + return result; + } + + + /** + * Find a single bean using its unique id. + */ + public T findId(OrmQueryRequest request) { + + T result = null; + + SpiTransaction t = request.getTransaction(); + + if (t.isBatchFlushOnQuery()){ + // before we perform a query, we need to flush any + // previous persist requests that are queued/batched. + // The query may read data affected by those requests. + t.flushBatch(); + } + + BeanFinder finder = request.getBeanFinder(); + if (finder != null) { + result = finder.find(request); + } else { + result = queryEngine.find(request); + } + + if (result != null && request.isUseBeanCache()){ + request.getBeanDescriptor().cachePutBeanData(result); + } + + return result; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java new file mode 100644 index 000000000..fa024f2a5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -0,0 +1,278 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.SqlQueryListener; +import com.avaje.ebean.SqlRow; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiSqlQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.RelationalQueryEngine; +import com.avaje.ebeaninternal.server.core.RelationalQueryRequest; +import com.avaje.ebeaninternal.server.jmx.MAdminLogging; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; + +/** + * Perform native sql fetches. + */ +public class DefaultRelationalQueryEngine implements RelationalQueryEngine { + + private static final Logger logger = Logger.getLogger(DefaultRelationalQueryEngine.class.getName()); + + private final int defaultMaxRows; + + private final Binder binder; + + private final String dbTrueValue; + + public DefaultRelationalQueryEngine(MAdminLogging logControl, Binder binder, String dbTrueValue) { + this.binder = binder; + this.defaultMaxRows = GlobalProperties.getInt("nativesql.defaultmaxrows",100000); + this.dbTrueValue = dbTrueValue == null ? "true" : dbTrueValue; + } + + public Object findMany(RelationalQueryRequest request) { + + SpiSqlQuery query = request.getQuery(); + + long startTime = System.currentTimeMillis(); + + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + ResultSet rset = null; + PreparedStatement pstmt = null; + + // flag indicating whether we need to close the resources... + boolean useBackgroundToContinueFetch = false; + + String sql = query.getQuery(); + + BindParams bindParams = query.getBindParams(); + + if (!bindParams.isEmpty()) { + // convert any named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + } + + try { + + String bindLog = ""; + String[] propNames = null; + + synchronized (query) { + if (query.isCancelled()){ + logger.finest("Query already cancelled"); + return null; + } + + // synchronise for query.cancel() support + pstmt = conn.prepareStatement(sql); + + if (query.getTimeout() > 0){ + pstmt.setQueryTimeout(query.getTimeout()); + } + if (query.getBufferFetchSizeHint() > 0){ + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + + if (!bindParams.isEmpty()) { + bindLog = binder.bind(bindParams, new DataBind(pstmt)); + } + + if (request.isLogSql()) { + String sOut = sql.replace(Constants.NEW_LINE, ' '); + sOut = sOut.replace(Constants.CARRIAGE_RETURN, ' '); + t.logInternal(sOut); + } + + rset = pstmt.executeQuery(); + + propNames = getPropertyNames(rset); + } + + // calculate the initialCapacity of the Map to reduce + // rehashing for queries with 12+ columns + float initCap = (propNames.length) / 0.7f; + int estimateCapacity = (int) initCap + 1; + + // determine the maxRows limit + int maxRows = defaultMaxRows; + if (query.getMaxRows() >= 1) { + maxRows = query.getMaxRows(); + } + + boolean hasHitMaxRows = false; + + int loadRowCount = 0; + + SqlQueryListener listener = query.getListener(); + + BeanCollectionWrapper wrapper = new BeanCollectionWrapper(request); + boolean isMap = wrapper.isMap(); + String mapKey = query.getMapKey(); + + SqlRow bean = null; + + while (rset.next()) { + synchronized (query) { + // synchronise for query.cancel() support + if (!query.isCancelled()){ + bean = readRow(request, rset, propNames, estimateCapacity); + } + } + if (bean != null){ + // bean can be null if query cancelled + if (listener != null) { + listener.process(bean); + + } else { + if (isMap) { + Object keyValue = bean.get(mapKey); + wrapper.addToMap(bean, keyValue); + } else { + wrapper.addToCollection(bean); + } + } + + loadRowCount++; + + if (loadRowCount == maxRows) { + // break, as we have hit the max rows to fetch... + hasHitMaxRows = true; + break; + } + } + } + + BeanCollection beanColl = wrapper.getBeanCollection(); + + if (hasHitMaxRows) { + if (rset.next()) { + // there are more rows available after the maxRows limit + beanColl.setHasMoreRows(true); + } + } + + if (!useBackgroundToContinueFetch) { + beanColl.setFinishedFetch(true); + } + + if (request.isLogSummary()) { + + long exeTime = System.currentTimeMillis() - startTime; + + String msg = "SqlQuery rows[" + loadRowCount + "] time[" + exeTime + "] bind[" + + bindLog + "] finished[" + beanColl.isFinishedFetch() + "]"; + + t.logInternal(msg); + } + + if (query.isCancelled()){ + logger.fine("Query was cancelled during execution rows:"+loadRowCount); + } + + return beanColl; + + } catch (Exception e) { + String m = Message.msg("fetch.error", e.getMessage(), sql); + throw new PersistenceException(m, e); + + } finally { + if (!useBackgroundToContinueFetch) { + try { + if (rset != null) { + rset.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, null, e); + } + } + } + } + + /** + * Build the list of property names. + */ + protected String[] getPropertyNames(ResultSet rset) throws SQLException { + + ArrayList propNames = new ArrayList(); + + ResultSetMetaData rsmd = rset.getMetaData(); + + int columnsPlusOne = rsmd.getColumnCount()+1; + + + for (int i = 1; i < columnsPlusOne; i++) { + String columnName = rsmd.getColumnLabel(i); + // will convert columnName to lower case + propNames.add(columnName); + } + + return (String[]) propNames.toArray(new String[propNames.size()]); + } + + /** + * Read the row from the ResultSet and return as a MapBean. + */ + protected SqlRow readRow(RelationalQueryRequest request, ResultSet rset, + String[] propNames, int initialCapacity) throws SQLException { + + // by default a map will rehash on the 12th entry + // it will be pretty common to have 12 or more entries so + // to reduce rehashing I am trying to estimate a good + // initial capacity for the MapBean to use. + SqlRow bean = new DefaultSqlRow(initialCapacity, 0.75f, dbTrueValue); + + int index = 0; + + for (int i = 0; i < propNames.length; i++) { + index++; + Object value = rset.getObject(index); + bean.set(propNames[i], value); + } + + return bean; + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java new file mode 100644 index 000000000..f71d4edb1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java @@ -0,0 +1,226 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * Used to return raw SQL query results. + *

    + * Refer to {@link SqlQuery} for examples. + *

    + *

    + * There are convenience methods such as getInteger(), getBigDecimal() etc. The + * reason for these methods is that the values put into this map often come + * straight from the JDBC resultSet. Depending on the JDBC driver it may put a + * different type into a given property. For example an Integer, BigDecimal, + * Double could all be put into a property depending on the JDBC driver used. + * These convenience methods automatically convert the value as required + * returning the type you expect. + *

    + */ +public class DefaultSqlRow implements SqlRow { + + static final long serialVersionUID = -3120927797041336242L; + + private final String dbTrueValue; + + /** + * The underlying map of property data. + */ + Map map; + + /** + * Create with a specific Map implementation. + *

    + * The default Map implementation is LinkedHashMap. + *

    + */ + public DefaultSqlRow(Map map, String dbTrueValue) { + this.map = map; + this.dbTrueValue = dbTrueValue; + } + + /** + * Create a new MapBean based on a LinkedHashMap with default + * initialCapacity (of 16). + */ + public DefaultSqlRow(String dbTrueValue) { + this.map = new LinkedHashMap(); + this.dbTrueValue = dbTrueValue; + } + + /** + * Create with an initialCapacity and loadFactor. + *

    + * The defaults of these are 16 and 0.75. + *

    + *

    + * Note that the Map will rehash the contents when the number of keys in + * this map reaches its threshold (initialCapacity * loadFactor). + *

    + */ + public DefaultSqlRow(int initialCapacity, float loadFactor, String dbTrueValue) { + this.map = new LinkedHashMap(initialCapacity, loadFactor); + this.dbTrueValue = dbTrueValue; + } + + public Iterator keys() { + return map.keySet().iterator(); + } + + public Object remove(Object name) { + name = ((String) name).toLowerCase(); + return map.remove(name); + } + + public Object get(Object name) { + name = ((String) name).toLowerCase(); + return map.get(name); + } + + public Object put(String name, Object value) { + return setInternal(name, value); + } + + public Object set(String name, Object value) { + return setInternal(name, value); + } + + private Object setInternal(String name, Object newValue) { + // MapBean properties are always lowercase + name = name.toLowerCase(); + + // valueList = null; + return map.put(name, newValue); + } + + public UUID getUUID(String name) { + Object val = get(name); + return BasicTypeConverter.toUUID(val); + } + + public Boolean getBoolean(String name) { + Object val = get(name); + return BasicTypeConverter.toBoolean(val, dbTrueValue); + } + + public Integer getInteger(String name) { + Object val = get(name); + return BasicTypeConverter.toInteger(val); + } + + public BigDecimal getBigDecimal(String name) { + Object val = get(name); + return BasicTypeConverter.toBigDecimal(val); + } + + public Long getLong(String name) { + Object val = get(name); + return BasicTypeConverter.toLong(val); + } + + public Double getDouble(String name) { + Object val = get(name); + return BasicTypeConverter.toDouble(val); + } + + public Float getFloat(String name) { + Object val = get(name); + return BasicTypeConverter.toFloat(val); + } + + public String getString(String name) { + Object val = get(name); + return BasicTypeConverter.toString(val); + } + + public java.util.Date getUtilDate(String name) { + Object val = get(name); + return BasicTypeConverter.toUtilDate(val); + } + + public Date getDate(String name) { + Object val = get(name); + return BasicTypeConverter.toDate(val); + } + + public Timestamp getTimestamp(String name) { + Object val = get(name); + return BasicTypeConverter.toTimestamp(val); + } + + public String toString() { + return map.toString(); + } + + // ------------------------------------ + // Normal map methods... + + public void clear() { + map.clear(); + } + + public boolean containsKey(Object key) { + key = ((String) key).toLowerCase(); + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Set keySet() { + return map.keySet(); + } + + public void putAll(Map t) { + map.putAll(t); + } + + public int size() { + return map.size(); + } + + public Collection values() { + return map.values(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetList.java b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetList.java new file mode 100644 index 000000000..265db00c5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetList.java @@ -0,0 +1,244 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import com.avaje.ebean.Page; + +public class LimitOffsetList implements List { + + private final LimitOffsetPagingQuery owner; + + private List localCopy; + + public LimitOffsetList(LimitOffsetPagingQuery owner) { + this.owner = owner; + } + + private void ensureLocalCopy() { + if (localCopy == null){ + localCopy = new ArrayList(); + + int pgIndex = 0; + while(true){ + Page page = owner.getPage(pgIndex++); + List list = page.getList(); + localCopy.addAll(list); + if (!page.hasNext()){ + break; + } + } + } + } + + private boolean hasNext(int position){ + return owner.hasNext(position); + } + + public void clear() { + localCopy = new ArrayList(); + } + + public T get(int index) { + if (localCopy != null){ + return localCopy.get(index); + } else { + return owner.get(index); + } + } + + public boolean isEmpty() { + if (localCopy != null){ + return localCopy.isEmpty(); + } else { + return owner.getTotalRowCount() == 0; + } + } + + public int size() { + if (localCopy != null){ + return localCopy.size(); + } else { + return owner.getTotalRowCount(); + } + } + + public Iterator iterator() { + if (localCopy != null){ + return localCopy.iterator(); + } else { + return new ListItr(this, 0); + } + } + + public ListIterator listIterator() { + if (localCopy != null){ + return localCopy.listIterator(); + } else { + return new ListItr(this, 0); + } + } + + public ListIterator listIterator(int index) { + if (localCopy != null){ + return localCopy.listIterator(index); + } else { + return new ListItr(this, index); + } + } + + public List subList(int fromIndex, int toIndex) { + if (localCopy != null){ + return localCopy.subList(fromIndex, toIndex); + } else { + //FIXME: subList not implemented ... + throw new RuntimeException("Not implemented at this point"); + } + } + + public int lastIndexOf(Object o) { + ensureLocalCopy(); + return localCopy.lastIndexOf(o); + } + + public void add(int index, T element) { + ensureLocalCopy(); + localCopy.add(index, element); + } + + public boolean add(T o) { + ensureLocalCopy(); + return localCopy.add(o); + } + + public boolean addAll(Collection c) { + ensureLocalCopy(); + return localCopy.addAll(c); + } + + public boolean addAll(int index, Collection c) { + ensureLocalCopy(); + return localCopy.addAll(index, c); + } + + public boolean contains(Object o) { + ensureLocalCopy(); + return localCopy.contains(o); + } + + public boolean containsAll(Collection c) { + ensureLocalCopy(); + return localCopy.containsAll(c); + } + + public int indexOf(Object o) { + ensureLocalCopy(); + return localCopy.indexOf(o); + } + + public T remove(int index) { + ensureLocalCopy(); + return localCopy.remove(index); + } + + public boolean remove(Object o) { + ensureLocalCopy(); + return localCopy.remove(o); + } + + public boolean removeAll(Collection c) { + ensureLocalCopy(); + return localCopy.removeAll(c); + } + + public boolean retainAll(Collection c) { + ensureLocalCopy(); + return localCopy.retainAll(c); + } + + public T set(int index, T element) { + ensureLocalCopy(); + return localCopy.set(index, element); + } + + + public Object[] toArray() { + ensureLocalCopy(); + return localCopy.toArray(); + } + + public K[] toArray(K[] a) { + ensureLocalCopy(); + return localCopy.toArray(a); + } + + private class ListItr implements ListIterator { + + private LimitOffsetList ownerList; + private int position; + + ListItr(LimitOffsetList ownerList, int position) { + this.ownerList = ownerList; + this.position = position; + } + + public void add(T o) { + ownerList.add(position++, o); + } + + public boolean hasNext() { + return ownerList.hasNext(position); + } + + public boolean hasPrevious() { + return position > 0; + } + + public T next() { + return ownerList.get(position++); + } + + public int nextIndex() { + return position; + } + + public T previous() { + return get(--position); + } + + public int previousIndex() { + return position - 1; + } + + public void remove() { + throw new RuntimeException("Not supported yet"); + } + + public void set(T o) { + throw new RuntimeException("Not supported yet"); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPage.java b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPage.java new file mode 100644 index 000000000..8ed61ddef --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPage.java @@ -0,0 +1,127 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.FutureList; +import com.avaje.ebean.Page; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionTouched; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Page implementation based on limit offset types of queries. + * + * @author rbygrave + * + * @param + * the entity bean type + */ +public class LimitOffsetPage implements Page, BeanCollectionTouched { + + private final int pageIndex; + + private final LimitOffsetPagingQuery owner; + + private FutureList futureList; + + public LimitOffsetPage(int pageIndex, LimitOffsetPagingQuery owner) { + this.pageIndex = pageIndex; + this.owner = owner; + } + + public FutureList getFutureList() { + + if (futureList == null) { + SpiQuery originalQuery = owner.getSpiQuery(); + SpiQuery copy = originalQuery.copy(); + copy.setPersistenceContext(originalQuery.getPersistenceContext()); + + int pageSize = owner.getPageSize(); + copy.setFirstRow(pageIndex * pageSize); + copy.setMaxRows(pageSize); + copy.setBeanCollectionTouched(this); + futureList = owner.getServer().findFutureList(copy, null); + } + + return futureList; + } + + /** + * Perform fetch ahead when the list is first accessed. + */ + public void notifyTouched(BeanCollection c) { + if (c.hasMoreRows()) { + owner.fetchAheadIfRequired(pageIndex); + } + } + + public List getList() { + try { + return getFutureList().get(); + } catch (Exception e) { + throw new PersistenceException(e); + } + } + + @SuppressWarnings("unchecked") + public boolean hasNext() { + return ((BeanCollection) getList()).hasMoreRows(); + } + + public boolean hasPrev() { + return pageIndex > 0; + } + + public Page next() { + return owner.getPage(pageIndex + 1); + } + + public Page prev() { + return owner.getPage(pageIndex - 1); + } + + public int getPageIndex() { + return pageIndex; + } + + public int getTotalPageCount() { + return owner.getTotalPageCount(); + } + + public int getTotalRowCount() { + return owner.getTotalRowCount(); + } + + public String getDisplayXtoYofZ(String to, String of) { + + int first = pageIndex * owner.getPageSize() + 1; + int last = first + getList().size() - 1; + int total = getTotalRowCount(); + + return first+to+last+of+total; + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagingQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagingQuery.java new file mode 100644 index 000000000..18ca363ed --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagingQuery.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Page; +import com.avaje.ebean.PagingList; +import com.avaje.ebeaninternal.api.Monitor; +import com.avaje.ebeaninternal.api.SpiQuery; + +public class LimitOffsetPagingQuery implements PagingList { + + private transient EbeanServer server; + + private final SpiQuery query; + + private final List> pages = new ArrayList>(); + + private final Monitor monitor = new Monitor(); + + private final int pageSize; + + private boolean fetchAhead = true; + + private Future futureRowCount; + + public LimitOffsetPagingQuery(EbeanServer server, SpiQuery query, int pageSize) { + this.query = query; + this.pageSize = pageSize; + this.server = server; + } + + public EbeanServer getServer() { + return server; + } + + public void setServer(EbeanServer server) { + this.server = server; + } + + public SpiQuery getSpiQuery() { + return query; + } + + public PagingList setFetchAhead(boolean fetchAhead) { + this.fetchAhead = fetchAhead; + return this; + } + + public List getAsList() { + return new LimitOffsetList(this); + } + + public Future getFutureRowCount() { + synchronized (monitor) { + if (futureRowCount == null){ + futureRowCount = server.findFutureRowCount(query, null); + } + return futureRowCount; + } + } + + private LimitOffsetPage internalGetPage(int i){ + synchronized (monitor) { + int ps = pages.size(); + if (ps <= i){ + for (int j = ps; j <= i; j++) { + LimitOffsetPage p = new LimitOffsetPage(j, this); + pages.add(p); + } + } + return pages.get(i); + } + } + + protected void fetchAheadIfRequired(int pageIndex){ + synchronized (monitor) { + // Already checked in LimitOffsetPage that there is another page + if (fetchAhead){ + // fetchAhead is turned on so get the next page and trigger query + LimitOffsetPage nextPage = internalGetPage(pageIndex + 1); + nextPage.getFutureList(); + } + } + } + + public void refresh() { + synchronized (monitor) { + futureRowCount = null; + pages.clear(); + } + } + + public Page getPage(int i) { + return internalGetPage(i); + } + + protected boolean hasNext(int position){ + return position < getTotalRowCount(); + } + + protected T get(int rowIndex){ + int pg = rowIndex / pageSize; + int offset = rowIndex % pageSize; + + Page page = getPage(pg); + return page.getList().get(offset); + } + + public int getTotalPageCount() { + + int rowCount = getTotalRowCount(); + if (rowCount == 0){ + return 0; + } else { + return ((rowCount-1) / pageSize) + 1; + } + } + + public int getPageSize() { + return pageSize; + } + + public int getTotalRowCount() { + try { + return getFutureRowCount().get(); + } catch (Exception e) { + throw new PersistenceException(e); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/LoadedPropertiesCache.java b/src/main/java/com/avaje/ebeaninternal/server/query/LoadedPropertiesCache.java new file mode 100644 index 000000000..036ed1e6c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LoadedPropertiesCache.java @@ -0,0 +1,39 @@ +package com.avaje.ebeaninternal.server.query; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +public class LoadedPropertiesCache { + + static ConcurrentHashMap> cache = new ConcurrentHashMap>(250, 0.75f, 16); + + public static Set get(int partialHash, Set partialProps, BeanDescriptor desc){ + + int manyHash = desc.getNamesOfManyPropsHash(); + int totalHash = 37*partialHash + manyHash; + + Integer key = Integer.valueOf(totalHash); + + Set includedProps = cache.get(key); + + if (includedProps == null){ + // its not in the cache so build it + LinkedHashSet mergeNames = new LinkedHashSet(); + mergeNames.addAll(partialProps); + if (manyHash != 0){ + mergeNames.addAll(desc.getNamesOfManyProps()); + } + + // we want it to be immutable and cache it + includedProps = Collections.unmodifiableSet(mergeNames); + cache.put(key, includedProps); + } + + return includedProps; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java new file mode 100644 index 000000000..0be32338d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.FutureTask; + +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Default implementation of FutureIds. + */ +public class QueryFutureIds extends BaseFuture> implements FutureIds { + + private final SpiQuery query; + + public QueryFutureIds(SpiQuery query, FutureTask> futureTask) { + super(futureTask); + this.query = query; + } + + public Query getQuery() { + return query; + } + + public List getPartialIds() { + return query.getIdList(); + } + + public boolean cancel(boolean mayInterruptIfRunning) { + query.cancel(); + return super.cancel(mayInterruptIfRunning); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java new file mode 100644 index 000000000..834086e23 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.FutureTask; + +import com.avaje.ebean.FutureList; +import com.avaje.ebean.Query; + +/** + * Default implementation for FutureList. + */ +public class QueryFutureList extends BaseFuture> implements FutureList { + + private final Query query; + + + public QueryFutureList(Query query, FutureTask> futureTask) { + super(futureTask); + this.query = query; + } + + public Query getQuery() { + return query; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + query.cancel(); + return super.cancel(mayInterruptIfRunning); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java new file mode 100644 index 000000000..e3d5b67e2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.FutureTask; + +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.Query; + +/** + * Future implementation for the row count query. + */ +public class QueryFutureRowCount extends BaseFuture implements FutureRowCount { + + private final Query query; + + public QueryFutureRowCount(Query query, FutureTask futureTask) { + super(futureTask); + this.query = query; + } + + public Query getQuery() { + return query; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + query.cancel(); + return super.cancel(mayInterruptIfRunning); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java new file mode 100644 index 000000000..46c58268a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebean.config.dbplatform.SqlLimiter; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect; +import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; +import com.avaje.ebeaninternal.server.deploy.DeployParser; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; + +import javax.persistence.PersistenceException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Factory for SqlSelectClause based on raw sql. + *

    + * Its job is to execute the sql, read the meta data to determine the columns to + * bean property mapping. + *

    + */ +public class RawSqlSelectClauseBuilder { + + private static final Logger logger = Logger.getLogger(RawSqlSelectClauseBuilder.class.getName()); + + private final Binder binder; + + private final SqlLimiter dbQueryLimiter; + private final DatabasePlatform dbPlatform; + + public RawSqlSelectClauseBuilder(DatabasePlatform dbPlatform, Binder binder) { + + this.binder = binder; + this.dbQueryLimiter = dbPlatform.getSqlLimiter(); + this.dbPlatform = dbPlatform; + } + + /** + * Build based on the includes and using the BeanJoinTree. + */ + public CQuery build(OrmQueryRequest request) throws PersistenceException { + + SpiQuery query = request.getQuery(); + BeanDescriptor desc = request.getBeanDescriptor(); + + DeployNamedQuery namedQuery = desc.getNamedQuery(query.getName()); + DRawSqlSelect sqlSelect = namedQuery.getSqlSelect(); + + // create a parser for this specific SqlSelect... has to be really + // as each SqlSelect could have different table alias etc + DeployParser parser = sqlSelect.createDeployPropertyParser(); + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + // prepare and convert logical property names to dbColumns etc + predicates.prepareRawSql(parser); + + SqlTreeAlias alias = new SqlTreeAlias(sqlSelect.getTableAlias()); + predicates.parseTableAlias(alias); + + String sql = null; + try { + + boolean includeRowNumColumn = false; + String orderBy = sqlSelect.getOrderBy(predicates); + + // build the actual sql String + sql = sqlSelect.buildSql(orderBy, predicates, request); + if (query.hasMaxRowsOrFirstRow() && dbQueryLimiter != null) { + // wrap with a limit offset or ROW_NUMBER() etc + SqlLimitResponse limitSql = dbQueryLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform)); + includeRowNumColumn = limitSql.isIncludesRowNumberColumn(); + + sql = limitSql.getSql(); + } else { + // add back select keyword + // ... was removed to support dbQueryLimiter + sql = "select " + sql; + } + + SqlTree sqlTree = sqlSelect.getSqlTree(); + + CQueryPlan queryPlan = new CQueryPlan(sql, sqlTree, true, includeRowNumColumn, ""); + CQuery compiledQuery = new CQuery(request, predicates, queryPlan); + + return compiledQuery; + + } catch (Exception e) { + + String msg = "Error with " + desc.getFullName() + " query:\r" + sql; + logger.log(Level.SEVERE, msg); + throw new PersistenceException(e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java b/src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java new file mode 100644 index 000000000..8b7686679 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java @@ -0,0 +1,63 @@ +package com.avaje.ebeaninternal.server.query; + +public class SplitName { + + public static String add(String prefix, String name){ + if (prefix != null){ + return prefix+"."+name; + } else { + return name; + } + } + + /** + * Return the number of occurrences of char in name. + */ + public static int count(char c, String name){ + + int count = 0; + for (int i = 0; i < name.length(); i++) { + if (c == name.charAt(i)){ + count++; + } + } + return count; + } + + /** + * Return the parent part of the path. + */ + public static String parent(String name) { + if (name == null){ + return null; + } else { + String[] s = split(name, true); + return s[0]; + } + } + + public static String[] split(String name){ + return split(name, true); + } + + public static String[] splitBegin(String name){ + return split(name, false); + } + + private static String[] split(String name, boolean last){ + + int pos = last ? name.lastIndexOf('.') : name.indexOf('.'); + if (pos == -1){ + if (last){ + return new String[]{null, name}; + } else { + return new String[]{name, null}; + } + } else { + String s0 = name.substring(0, pos); + String s1 = name.substring(pos+1); + return new String[]{s0,s1}; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java new file mode 100644 index 000000000..ecf7f972f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.Set; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; + +/** + * Controls the loading of property data into a bean. + *

    + * Takes into account the differences of lazy loading and + * partial objects. + *

    + */ +public class SqlBeanLoad { + + private final DbReadContext ctx; + private final Object bean; + private final Class type; + private final Object originalOldValues; + private final boolean isLazyLoad; + + // set of properties to exclude from the refresh because it is + // not a refresh but rather a lazyLoading event. + private final Set excludes; + private final boolean setOriginalOldValues; + + private final boolean rawSql; + + public SqlBeanLoad(DbReadContext ctx, Class type, Object bean, Mode queryMode) { + + this.ctx = ctx; + this.rawSql = ctx.isRawSql(); + this.type = type; + this.isLazyLoad = queryMode.equals(Mode.LAZYLOAD_BEAN); + this.bean = bean; + + if (bean instanceof EntityBean) { + EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept(); + + this.excludes = isLazyLoad ? ebi.getLoadedProps() : null; + if (excludes != null) { + // lazy loading a "Partial Object"... which already + // contains some properties and perhaps some oldValues + // and these will need to be maintained... + originalOldValues = ebi.getOldValues(); + } else { + originalOldValues = null; + } + this.setOriginalOldValues = originalOldValues != null; + } else { + this.excludes = null; + this.originalOldValues = null; + this.setOriginalOldValues = false; + } + } + + /** + * Return true if this is a lazy loading. + */ + public boolean isLazyLoad() { + return isLazyLoad; + } + + /** + * Increment the resultSet index 1. + */ + public void loadIgnore(int increment) { + ctx.getDataReader().incrementPos(increment); + } + + public Object load(BeanProperty prop) throws SQLException { + + if (!rawSql && prop.isTransient()){ + return null; + } + + if ((bean == null) + || (excludes != null && excludes.contains(prop.getName())) + || (type != null && !prop.isAssignableFrom(type))){ + + // ignore this property + // ... null: bean already in persistence context + // ... excludes: partial bean that is lazy loading + // ... type: inheritance and not assignable to this instance + + prop.loadIgnore(ctx); + return null; + } + + try { + Object dbVal = prop.read(ctx); + if (isLazyLoad){ + prop.setValue(bean, dbVal); + } else { + prop.setValueIntercept(bean, dbVal); + } + if (setOriginalOldValues){ + // maintain original oldValues for partially loaded bean + prop.setValue(originalOldValues, dbVal); + } + return dbVal; + + } catch (Exception e) { + String msg = "Error loading on " + prop.getFullBeanName(); + throw new PersistenceException(msg, e); + } + } + + public void loadAssocMany(BeanPropertyAssocMany prop) { + + // do nothing, as a lazy loading BeanCollection 'reference' + // is created and registered with the loading context + // in SqlTreeNodeBean.createListProxies() + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlQueryFutureList.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlQueryFutureList.java new file mode 100644 index 000000000..43b6b7113 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlQueryFutureList.java @@ -0,0 +1,34 @@ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.FutureTask; + +import com.avaje.ebean.SqlFutureList; +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; + +/** + * Default implementation of SqlFutureList. + * + * @author rbygrave + */ +public class SqlQueryFutureList extends BaseFuture> implements SqlFutureList { + + private final SqlQuery query; + + public SqlQueryFutureList(SqlQuery query, FutureTask> futureTask) { + super(futureTask); + this.query = query; + } + + public SqlQuery getQuery() { + return query; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + query.cancel(); + return super.cancel(mayInterruptIfRunning); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java new file mode 100644 index 000000000..e9921e2b9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java @@ -0,0 +1,191 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Represents the SELECT clause part of the SQL query. + */ +public class SqlTree { + + private SqlTreeNode rootNode; + + + /** + * Property if resultSet contains master and detail rows. + */ + private BeanPropertyAssocMany manyProperty; + private String manyPropertyName; + private ElPropertyValue manyPropEl; + + private Set includes; + + /** + * Summary of the select being generated. + */ + private String summary; + + private String selectSql; + + private String fromSql; + + /** + * Encrypted Properties require additional binding. + */ + private BeanProperty[] encryptedProps; + + /** + * Where clause for inheritance. + */ + private String inheritanceWhereSql; + + + /** + * Create the SqlSelectClause. + */ + public SqlTree() { + } + + public List buildSelectExpressionChain() { + ArrayList list = new ArrayList(); + rootNode.buildSelectExpressionChain(list); + return list; + } + + /** + * Return the includes. Associated beans lists etc. + */ + public Set getIncludes() { + return includes; + } + + /** + * Set the association includes (Ones and Many's). + */ + public void setIncludes(Set includes) { + this.includes = includes; + } + + /** + * Set the manyProperty used for this query. + */ + public void setManyProperty(BeanPropertyAssocMany manyProperty, String manyPropertyName, ElPropertyValue manyPropEl) { + this.manyProperty = manyProperty; + this.manyPropertyName = manyPropertyName; + this.manyPropEl = manyPropEl; + } + + /** + * Return the String for the actual SQL. + */ + public String getSelectSql() { + return selectSql; + } + + /** + * Set the select sql clause. + */ + public void setSelectSql(String selectSql) { + this.selectSql = selectSql; + } + + + public String getFromSql() { + return fromSql; + } + + public void setFromSql(String fromSql) { + this.fromSql = fromSql; + } + + /** + * Return the where clause for inheritance. + */ + public String getInheritanceWhereSql() { + return inheritanceWhereSql; + } + + /** + * Set where clause(s) for inheritance. + */ + public void setInheritanceWhereSql(String whereSql) { + this.inheritanceWhereSql = whereSql; + } + + /** + * Set the summary description of the query. + */ + public void setSummary(String summary) { + this.summary = summary; + } + + /** + * Return a summary of the select clause. + */ + public String getSummary() { + return summary; + } + + public SqlTreeNode getRootNode() { + return rootNode; + } + + public void setRootNode(SqlTreeNode rootNode) { + this.rootNode = rootNode; + } + + /** + * Return the property that is associated with the many. There can only be + * one per SqlSelect. This can be null. + */ + public BeanPropertyAssocMany getManyProperty() { + return manyProperty; + } + + public String getManyPropertyName() { + return manyPropertyName; + } + + public ElPropertyValue getManyPropertyEl() { + return manyPropEl; + } + + /** + * Return true if this query includes a Many association. + */ + public boolean isManyIncluded() { + return (manyProperty != null); + } + + public BeanProperty[] getEncryptedProps() { + return encryptedProps; + } + + public void setEncryptedProps(BeanProperty[] encryptedProps) { + this.encryptedProps = encryptedProps; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java new file mode 100644 index 000000000..998e25551 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java @@ -0,0 +1,223 @@ +package com.avaje.ebeaninternal.server.query; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; + +/** + * Special Map of the logical property joins to table alias. + * + * @author rbygrave + */ +public class SqlTreeAlias { + + private int counter; + + private int manyWhereCounter; + + private TreeSet joinProps = new TreeSet(); + + private HashSet embeddedPropertyJoins; + + private TreeSet manyWhereJoinProps = new TreeSet(); + + private HashMap aliasMap = new HashMap(); + + private HashMap manyWhereAliasMap = new HashMap(); + + private final String rootTableAlias; + + public SqlTreeAlias(String rootTableAlias) { + this.rootTableAlias = rootTableAlias; + } + + /** + * Add joins to support where predicates + * @param manyWhereJoins + */ + public void addManyWhereJoins(Set manyWhereJoins) { + if (manyWhereJoins != null){ + for (String include : manyWhereJoins) { + addPropertyJoin(include, manyWhereJoinProps); + } + } + } + + private void addEmbeddedPropertyJoin(String embProp){ + if (embeddedPropertyJoins == null){ + embeddedPropertyJoins = new HashSet(); + } + embeddedPropertyJoins.add(embProp); + } + + /** + * Add joins. + */ + public void addJoin(Set propJoins, BeanDescriptor desc) { + if (propJoins != null){ + for (String propJoin : propJoins) { + ElPropertyDeploy elProp = desc.getElPropertyDeploy(propJoin); + if (elProp != null && elProp.getBeanProperty().isEmbedded()) { + String[] split = SplitName.split(propJoin); + addPropertyJoin(split[0], joinProps); + addEmbeddedPropertyJoin(propJoin); + + } else { + addPropertyJoin(propJoin, joinProps); + } + } + } + } + + + + private void addPropertyJoin(String include, TreeSet set){ + if (set.add(include)) { + String[] split = SplitName.split(include); + if (split[0] != null){ + addPropertyJoin(split[0], set); + } + } + } + + /** + * Build a set of table alias for the given bean and fetch + * joined properties. + */ + public void buildAlias() { + + Iterator i = joinProps.iterator(); + while (i.hasNext()) { + calcAlias(i.next()); + } + + i = manyWhereJoinProps.iterator(); + while (i.hasNext()) { + calcAliasManyWhere(i.next()); + } + + mapEmbeddedPropertyAlias(); + } + + private void mapEmbeddedPropertyAlias() { + if (embeddedPropertyJoins != null){ + for (String propJoin : embeddedPropertyJoins) { + String[] split = SplitName.split(propJoin); + // the table alias of the parent path + String alias = getTableAlias(split[0]); + aliasMap.put(propJoin, alias); + } + } + } + + private String calcAlias(String prefix) { + + String alias = nextTableAlias(); + aliasMap.put(prefix, alias); + return alias; + } + + private String calcAliasManyWhere(String prefix) { + + String alias = nextManyWhereTableAlias(); + manyWhereAliasMap.put(prefix, alias); + return alias; + } + + /** + * Return the table alias for a given property name. + */ + public String getTableAlias(String prefix){ + if (prefix == null){ + return rootTableAlias; + } else { + String s = aliasMap.get(prefix); + if (s == null){ + return calcAlias(prefix); + } + return s; + } + } + + /** + * Return an alias using "Many where joins". + */ + public String getTableAliasManyWhere(String prefix) { + if (prefix == null){ + return rootTableAlias; + } + String s = manyWhereAliasMap.get(prefix); + if (s == null){ + s = aliasMap.get(prefix); + } + if (s == null) { + String msg = "Could not determine table alias for [" + prefix + "] manyMap[" + + manyWhereAliasMap + "] aliasMap[" + aliasMap + "]"; + throw new RuntimeException(msg); + } + return s; + } + + /** + * Parse for where clauses that uses "Many where joins" + */ + public String parseWhere(String clause) { + clause = parseRootAlias(clause); + clause = parseAliasMap(clause, manyWhereAliasMap); + return parseAliasMap(clause, aliasMap); + } + + /** + * Parse without using any extra "Many where joins". + */ + public String parse(String clause) { + clause = parseRootAlias(clause); + return parseAliasMap(clause, aliasMap); + } + + /** + * Parse the clause replacing the table alias place holders. + */ + private String parseRootAlias(String clause) { + + if (rootTableAlias == null){ + return clause.replace("${}", ""); + } else { + return clause.replace("${}", rootTableAlias+"."); + } + } + + /** + * Parse the clause replacing the table alias place holders. + */ + private String parseAliasMap(String clause, HashMap parseAliasMap) { + + Iterator> i = parseAliasMap.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = i.next(); + String k = "${"+e.getKey()+"}"; + clause = clause.replace(k, e.getValue()+"."); + } + + return clause; + } + + + /** + * Return the next valid table alias given the preferred table alias. + */ + private String nextTableAlias() { + return "t"+(++counter); + } + + private String nextManyWhereTableAlias() { + return "u"+(++manyWhereCounter); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java new file mode 100644 index 000000000..a56f87443 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -0,0 +1,683 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Type; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +/** + * Factory for SqlTree. + */ +public class SqlTreeBuilder { + + private static final Logger logger = Logger.getLogger(SqlTreeBuilder.class.getName()); + + private final SpiQuery query; + + private final BeanDescriptor desc; + + private final OrmQueryDetail queryDetail; + + private final StringBuilder summary = new StringBuilder(); + + private final CQueryPredicates predicates; + + private final boolean subQuery; + + /** + * Property if resultSet contains master and detail rows. + */ + private BeanPropertyAssocMany manyProperty; + + private String manyPropertyName; + + private final SqlTreeAlias alias; + + private final DefaultDbSqlContext ctx; + + private final HashSet selectIncludes = new HashSet(); + + private final ManyWhereJoins manyWhereJoins; + + private final TableJoin includeJoin; + + private final boolean rawSql; + + /** + * Construct for RawSql query. + */ + public SqlTreeBuilder(OrmQueryRequest request, CQueryPredicates predicates, OrmQueryDetail queryDetail) { + + this.rawSql = true; + this.desc = request.getBeanDescriptor(); + this.query = null; + this.subQuery = false; + this.queryDetail = queryDetail; + this.predicates = predicates; + + this.includeJoin = null; + this.manyWhereJoins = null; + this.alias = null; + this.ctx = null; + } + + /** + * The predicates are used to determine if 'extra' joins are required to + * support the where and/or order by clause. If so these extra joins are + * added to the root node. + */ + public SqlTreeBuilder(String tableAliasPlaceHolder, String columnAliasPrefix, OrmQueryRequest request, CQueryPredicates predicates) { + + this.rawSql = false; + this.desc = request.getBeanDescriptor(); + this.query = request.getQuery(); + + this.subQuery = Type.SUBQUERY.equals(query.getType()); + this.includeJoin = query.getIncludeTableJoin(); + this.manyWhereJoins = query.getManyWhereJoins(); + this.queryDetail = query.getDetail(); + + this.predicates = predicates; + this.alias = new SqlTreeAlias(request.getBeanDescriptor().getBaseTableAlias()); + this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery); + } + + /** + * Build based on the includes and using the BeanJoinTree. + */ + public SqlTree build() { + + SqlTree sqlTree = new SqlTree(); + + summary.append(desc.getName()); + + // build the appropriate chain of SelectAdapter's + buildRoot(desc, sqlTree); + + // build the actual String + SqlTreeNode rootNode = sqlTree.getRootNode(); + + if (!rawSql){ + sqlTree.setSelectSql(buildSelectClause(rootNode)); + sqlTree.setFromSql(buildFromClause(rootNode)); + sqlTree.setInheritanceWhereSql(buildWhereClause(rootNode)); + sqlTree.setEncryptedProps(ctx.getEncryptedProps()); + } + sqlTree.setIncludes(queryDetail.getIncludes()); + sqlTree.setSummary(summary.toString()); + + if (manyPropertyName != null){ + ElPropertyValue manyPropEl = desc.getElGetValue(manyPropertyName); + sqlTree.setManyProperty(manyProperty, manyPropertyName, manyPropEl); + } + + return sqlTree; + } + + private String buildSelectClause(SqlTreeNode rootNode) { + + if (rawSql){ + return "Not Used"; + } + rootNode.appendSelect(ctx, subQuery); + + String selectSql = ctx.getContent(); + + // trim off the first comma + if (selectSql.length() >= SqlTreeNode.COMMA.length()) { + selectSql = selectSql.substring(SqlTreeNode.COMMA.length()); + } + + return selectSql; + } + + private String buildWhereClause(SqlTreeNode rootNode) { + + if (rawSql){ + return "Not Used"; + } + rootNode.appendWhere(ctx); + return ctx.getContent(); + } + + private String buildFromClause(SqlTreeNode rootNode) { + + if (rawSql){ + return "Not Used"; + } + rootNode.appendFrom(ctx, false); + return ctx.getContent(); + } + + private void buildRoot(BeanDescriptor desc, SqlTree sqlTree) { + + SqlTreeNode selectRoot = buildSelectChain(null, null, desc, null); + sqlTree.setRootNode(selectRoot); + + if (!rawSql){ + alias.addJoin(queryDetail.getIncludes(), desc); + alias.addJoin(predicates.getPredicateIncludes(), desc); + alias.addManyWhereJoins(manyWhereJoins.getJoins()); + + // build set of table alias + alias.buildAlias(); + + predicates.parseTableAlias(alias); + } + } + + /** + * Recursively build the query tree depending on what leaves in the tree + * should be included. + */ + private SqlTreeNode buildSelectChain(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, List joinList) { + + List myJoinList = new ArrayList(); + + BeanPropertyAssocOne[] ones = desc.propertiesOne(); + for (int i = 0; i < ones.length; i++) { + String propPrefix = SplitName.add(prefix, ones[i].getName()); + if (isIncludeBean(propPrefix, ones[i])) { + selectIncludes.add(propPrefix); + buildSelectChain(propPrefix, ones[i], ones[i].getTargetDescriptor(), myJoinList); + } + } + + BeanPropertyAssocMany[] manys = desc.propertiesMany(); + for (int i = 0; i < manys.length; i++) { + String propPrefix = SplitName.add(prefix, manys[i].getName()); + if (isIncludeMany(prefix, propPrefix, manys[i])) { + selectIncludes.add(propPrefix); + buildSelectChain(propPrefix, manys[i], manys[i].getTargetDescriptor(), myJoinList); + } + } + + if (prefix == null && !rawSql) { + addManyWhereJoins(myJoinList); + } + + SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList); + if (joinList != null) { + joinList.add(selectNode); + } + return selectNode; + } + + /** + * Add joins used to support where clause predicates on 'many' properties. + *

    + * These joins are effectively independent of any fetch joins on 'many' properties. + *

    + */ + private void addManyWhereJoins(List myJoinList) { + + Set includes = manyWhereJoins.getJoins(); + for (String joinProp : includes) { + + BeanPropertyAssoc beanProperty = (BeanPropertyAssoc) desc.getBeanPropertyFromPath(joinProp); + SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp, beanProperty); + myJoinList.add(nodeJoin); + } + } + + private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, List myList) { + + OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false); + + SqlTreeProperties props = getBaseSelect(desc, queryProps); + + if (prefix == null) { + buildExtraJoins(desc, myList); + return new SqlTreeNodeRoot(desc, props, myList, !subQuery, includeJoin); + + } else if (prop instanceof BeanPropertyAssocMany) { + return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany) prop, props, myList); + + } else { + return new SqlTreeNodeBean(prefix, prop, props, myList, true); + } + } + + /** + * Build extra joins to support properties used in where clause but not + * already in select clause. + */ + private void buildExtraJoins(BeanDescriptor desc, List myList) { + + if (rawSql){ + return; + } + + Set predicateIncludes = predicates.getPredicateIncludes(); + + if (predicateIncludes == null) { + return; + } + + // Note includes - basically means joins. + // The selectIncludes is the set of joins that are required to support + // the 'select' part of the query. We may need to add other joins to + // support the predicates or order by clauses. + + // remove ManyWhereJoins from the predicateIncludes + predicateIncludes.removeAll(manyWhereJoins.getJoins()); + + // look for predicateIncludes that are not in selectIncludes and add + // them as extra joins to the query + IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes); + + Collection extraJoins = extraJoinDistill.getExtraJoinRootNodes(); + if (extraJoins.isEmpty()) { + return; + + } else { + // add extra joins required to support predicates + // and/or order by clause + Iterator it = extraJoins.iterator(); + while (it.hasNext()) { + SqlTreeNodeExtraJoin extraJoin = it.next(); + myList.add(extraJoin); + + if (extraJoin.isManyJoin()) { + // as we are now going to join to the many then we need + // to add the distinct to the sql query to stop duplicate + // rows... + query.setDistinct(true); + } + } + } + } + + /** + * A subQuery has slightly different rules in that it just generates SQL + * (into the where clause) and its properties are not required to read the + * resultSet etc. + *

    + * This means it can included individual properties of an embedded bean. + *

    + */ + private void addPropertyToSubQuery(SqlTreeProperties selectProps, BeanDescriptor desc, OrmQueryProperties queryProps, String propName) { + + BeanProperty p = desc.findBeanProperty(propName); + if (p == null) { + logger.log(Level.SEVERE, "property [" + propName + "]not found on " + desc + " for query - excluding it."); + + } + else if (p instanceof BeanPropertyAssoc && p.isEmbedded()) { + // if the property is embedded we need to lookup the real column name + int pos = propName.indexOf("."); + if (pos > -1) { + String name = propName.substring(pos + 1); + p = ((BeanPropertyAssoc) p).getTargetDescriptor().findBeanProperty(name); + } + } + + selectProps.add(p); + } + + private void addProperty(SqlTreeProperties selectProps, BeanDescriptor desc, OrmQueryProperties queryProps, String propName) { + + if (subQuery) { + addPropertyToSubQuery(selectProps, desc, queryProps, propName); + return; + } + + int basePos = propName.indexOf('.'); + if (basePos > -1) { + // property on an embedded bean. Embedded beans do not yet + // support being partially populated so we include the + // 'base' property and make sure we only do that once + String baseName = propName.substring(0, basePos); + + // make sure we only included the base/embedded bean once + if (!selectProps.containsProperty(baseName)) { + BeanProperty p = desc.findBeanProperty(baseName); + if (p == null) { + String m = "property [" + propName + "] not found on " + desc + " for query - excluding it."; + logger.log(Level.SEVERE, m); + + } else if (p.isEmbedded()) { + // add the embedded bean (and effectively + // all its properties) + selectProps.add(p); + // also make sure it is added to included properties + // to avoid unnecessary lazy loading + selectProps.getIncludedProperties().add(baseName); + + } else { + String m = "property [" + p.getFullBeanName() + + "] expected to be an embedded bean for query - excluding it."; + logger.log(Level.SEVERE, m); + } + } + + } else { + // find the property including searching the + // sub class hierarchy if required + BeanProperty p = desc.findBeanProperty(propName); + if (p == null) { + logger.log(Level.SEVERE, "property [" + propName + "] not found on " + desc + + " for query - excluding it."); + + } else if (p.isId()) { + // do not bother to include id for normal queries as the + // id is always added (except for subQueries) + + } else if (p instanceof BeanPropertyAssoc) { + // need to check if this property should be + // excluded. This occurs when this property is + // included as a bean join. With a bean join + // the property should be excluded as the bean + // join has its own node in the SqlTree. + if (!queryProps.isIncludedBeanJoin(p.getName())) { + // include the property... which basically + // means include the foreign key column(s) + selectProps.add(p); + } + } else { + selectProps.add(p); + } + } + } + + private SqlTreeProperties getBaseSelectPartial(BeanDescriptor desc, OrmQueryProperties queryProps) { + + SqlTreeProperties selectProps = new SqlTreeProperties(); + selectProps.setReadOnly(queryProps.isReadOnly()); + selectProps.setIncludedProperties(queryProps.getAllIncludedProperties()); + + // add properties in the order in which they appear + // in the query. Gives predictable sql/properties for + // use with SqlSelect type queries. + + // Also note that this can include transient properties. + // This makes sense for transient properties used to + // hold sum() count() type values (with SqlSelect) + Iterator it = queryProps.getSelectProperties(); + while (it.hasNext()) { + String propName = it.next(); + if (propName.length() > 0) { + addProperty(selectProps, desc, queryProps, propName); + } + } + + return selectProps; + } + + private SqlTreeProperties getBaseSelect(BeanDescriptor desc, OrmQueryProperties queryProps) { + + boolean partial = queryProps != null && !queryProps.allProperties(); + if (partial) { + return getBaseSelectPartial(desc, queryProps); + } + + SqlTreeProperties selectProps = new SqlTreeProperties(); + + // normal simple properties of the bean + selectProps.add(desc.propertiesBaseScalar()); + selectProps.add(desc.propertiesBaseCompound()); + selectProps.add(desc.propertiesEmbedded()); + + BeanPropertyAssocOne[] propertiesOne = desc.propertiesOne(); + for (int i = 0; i < propertiesOne.length; i++) { + if (queryProps != null && queryProps.isIncludedBeanJoin(propertiesOne[i].getName())) { + // if it is a joined bean... then don't add the property + // as it will have its own entire Node in the SqlTree + } else { + selectProps.add(propertiesOne[i]); + } + } + + selectProps.setTableJoins(desc.tableJoins()); + + InheritInfo inheritInfo = desc.getInheritInfo(); + if (inheritInfo != null) { + // add sub type properties + inheritInfo.addChildrenProperties(selectProps); + + } + return selectProps; + } + + /** + * Return true if this many node should be included in the query. + */ + private boolean isIncludeMany(String prefix, String propName, BeanPropertyAssocMany manyProp) { + + if (queryDetail.isJoinsEmpty()) { + return false; + } + + if (queryDetail.includes(propName)) { + + if (manyProperty != null) { + // only one many associated allowed to be included in fetch + if (logger.isLoggable(Level.FINE)) { + String msg = "Not joining [" + propName + "] as already joined to a Many[" + manyProperty + "]."; + logger.fine(msg); + } + return false; + } + + manyProperty = manyProp; + manyPropertyName = propName; + summary.append(" +many:").append(propName); + return true; + } + return false; + } + + /** + * Test to see if we are including this node into the query. + *

    + * Return true if this node is FULLY included resulting in table join. If + * the node is not included but its parent has been included then a "bean + * proxy" is added and false is returned. + *

    + */ + private boolean isIncludeBean(String prefix, BeanPropertyAssocOne prop) { + + if (queryDetail.includes(prefix)) { + // explicitly included + summary.append(", ").append(prefix); + String[] splitNames = SplitName.split(prefix); + queryDetail.includeBeanJoin(splitNames[0], splitNames[1]); + return true; + } + + return false; + } + + /** + * Takes the select includes and the predicates includes and determines the + * extra joins required to support the predicates (that are not already + * supported by the select includes). + *

    + * This returns ONLY the leaves. The joins for the leaves + *

    + */ + private static class IncludesDistiller { + + private final Set selectIncludes; + private final Set predicateIncludes; + + /** + * Contains the 'root' extra joins. We only return the roots back. + */ + private final Map joinRegister = new HashMap(); + + /** + * Register of all the extra join nodes. + */ + private final Map rootRegister = new HashMap(); + + private final BeanDescriptor desc; + + private IncludesDistiller(BeanDescriptor desc, Set selectIncludes, Set predicateIncludes) { + this.desc = desc; + this.selectIncludes = selectIncludes; + this.predicateIncludes = predicateIncludes; + } + + /** + * Build the collection of extra joins returning just the roots. + *

    + * each root returned here could contain a little tree of joins. This + * follows the more natural pattern and allows for forcing outer joins + * from a join to a 'many' down through the rest of its tree. + *

    + */ + private Collection getExtraJoinRootNodes() { + + String[] extras = findExtras(); + if (extras.length == 0) { + return rootRegister.values(); + } + + // sort so we process only getting the leaves + // excluding nodes between root and the leaf + Arrays.sort(extras); + + // reverse order so get the leaves first... + for (int i = 0; i < extras.length; i++) { + createExtraJoin(extras[i]); + } + + return rootRegister.values(); + } + + private void createExtraJoin(String includeProp) { + + SqlTreeNodeExtraJoin extraJoin = createJoinLeaf(includeProp); + if (extraJoin != null) { + // add the extra join... + + // find root of this extra join... linking back to the + // parents (creating the tree) as it goes. + SqlTreeNodeExtraJoin root = findExtraJoinRoot(includeProp, extraJoin); + + // register the root because these are the only ones we + // return back. + rootRegister.put(root.getName(), root); + } + } + + /** + * Create a SqlTreeNodeExtraJoin, register and return it. + */ + private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) { + + ElPropertyValue elGetValue = desc.getElGetValue(propertyName); + + if (elGetValue == null) { + // this can occur for master detail queries + // with concatenated keys (so not an error now) + return null; + } + BeanProperty beanProperty = elGetValue.getBeanProperty(); + if (beanProperty instanceof BeanPropertyAssoc) { + BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; + if (assocProp.isEmbedded()) { + // no extra join required for embedded beans + return null; + } + SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp); + joinRegister.put(propertyName, extraJoin); + return extraJoin; + } + return null; + } + + /** + * Find the root the this extra join tree. + *

    + * This may need to create a parent join implicitly if a predicate join + * 'skips' a level. e.g. where details.user.id = 1 (maybe join to + * details is not specified and is implicitly created. + *

    + */ + private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp, SqlTreeNodeExtraJoin childJoin) { + + int dotPos = includeProp.lastIndexOf('.'); + if (dotPos == -1) { + // no parent possible(parent is root) + return childJoin; + + } else { + // look in register ... + String parentPropertyName = includeProp.substring(0, dotPos); + if (selectIncludes.contains(parentPropertyName)) { + // parent already handled by select + return childJoin; + } + + SqlTreeNodeExtraJoin parentJoin = joinRegister.get(parentPropertyName); + if (parentJoin == null) { + // we need to create this the parent implicitly... + parentJoin = createJoinLeaf(parentPropertyName); + } + + parentJoin.addChild(childJoin); + return findExtraJoinRoot(parentPropertyName, parentJoin); + } + } + + /** + * Find the extra joins required by predicates and not already taken + * care of by the select. + */ + private String[] findExtras() { + + List extras = new ArrayList(); + + for (String predProp : predicateIncludes) { + if (!selectIncludes.contains(predProp)) { + extras.add(predProp); + } + } + return extras.toArray(new String[extras.size()]); + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java new file mode 100644 index 000000000..a9abb27ae --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java @@ -0,0 +1,55 @@ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; + +public interface SqlTreeNode { + + /** + * the new line character used. + *

    + * Note that this is removed for logging sql to the transaction log. + *

    + */ + public static final char NEW_LINE = '\n'; + + public static final String PERIOD = "."; + + public static final String COMMA = ", "; + + public static final int NORMAL = 0; + public static final int SHARED = 1; + public static final int READONLY = 2; + + public void buildSelectExpressionChain(List selectChain); + + /** + * Append the required column information to the SELECT part of the sql + * statement. + */ + public void appendSelect(DbSqlContext ctx, boolean subQuery); + + /** + * Append to the FROM part of the sql. + */ + public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin); + + /** + * Append any where predicates for inheritance. + */ + public void appendWhere(DbSqlContext ctx); + + /** + * Load the appropriate information from the SqlSelectReader. + *

    + * At a high level this actually controls the reading of the data from the + * jdbc resultSet and putting it into the bean etc. + *

    + * + */ + public void load(DbReadContext ctx, Object parentBean) throws SQLException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java new file mode 100644 index 000000000..3cd20392f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -0,0 +1,541 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Normal bean included in the query. + */ +public class SqlTreeNodeBean implements SqlTreeNode { + + private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; + + final BeanDescriptor desc; + + final IdBinder idBinder; + + /** + * The children which will be other SelectBean or SelectProxyBean. + */ + final SqlTreeNode[] children; + + final boolean readOnlyLeaf; + + /** + * Set to true if this is a partial object fetch. + */ + final boolean partialObject; + + /** + * The set of properties explicitly included in the query. + * We actually add the manyProp names to this as they are + * references/proxies we add via createListProxies(). + */ + final Set partialProps; + + /** + * The hash of the partialProps (calculate once). + */ + final int partialHash; + + final BeanProperty[] properties; + + /** + * Extra where clause added by Where annotation on associated many. + */ + final String extraWhere; + + final BeanPropertyAssoc nodeBeanProp; + + final TableJoin[] tableJoins; + + /** + * False if report bean and has no id property. + */ + final boolean readId; + + final boolean disableLazyLoad; + + final InheritInfo inheritInfo; + + final String prefix; + + final Set includedProps; + + final Map pathMap; + + public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, + SqlTreeProperties props, List myChildren, boolean withId) { + + this(prefix, beanProp, beanProp.getTargetDescriptor(),props, myChildren, withId); + } + + /** + * Create with the appropriate node. + */ + public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, BeanDescriptor desc, + SqlTreeProperties props, List myChildren, boolean withId) { + + this.prefix = prefix; + this.nodeBeanProp = beanProp; + this.desc = desc; + this.inheritInfo = desc.getInheritInfo(); + this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere(); + + this.idBinder = desc.getIdBinder(); + + // the bean has an Id property and we want to use it + this.readId = withId && (desc.propertiesId().length > 0); + this.disableLazyLoad = !readId || desc.isSqlSelectBased(); + + this.tableJoins = props.getTableJoins(); + + this.partialObject = props.isPartialObject(); + this.partialProps = props.getIncludedProperties(); + this.partialHash = partialObject ? partialProps.hashCode() : 0; + + this.readOnlyLeaf = props.isReadOnly(); + + this.properties = props.getProps(); + + if (partialObject){ + // merge the explicit partialProps with the implicitly added + // list proxies (that are added by createListProxies()) to get + // the full set of 'loaded' properties for this bean. + includedProps = LoadedPropertiesCache.get(partialHash, partialProps, desc); + } else { + includedProps = null; + } + + if (myChildren == null) { + children = NO_CHILDREN; + } else { + children = myChildren.toArray(new SqlTreeNode[myChildren.size()]); + } + + pathMap = createPathMap(prefix, desc); + } + + private Map createPathMap(String prefix, BeanDescriptor desc) { + + BeanPropertyAssocMany[] manys = desc.propertiesMany(); + + HashMap m = new HashMap(); + for (int i = 0; i < manys.length; i++) { + String name = manys[i].getName(); + m.put(name, getPath(prefix, name)); + } + + return m; + } + + private String getPath(String prefix, String propertyName){ + if (prefix == null){ + return propertyName; + } else { + return prefix+"."+propertyName; + } + } + + protected void postLoad(DbReadContext cquery, Object loadedBean, Object id) { + } + + public void buildSelectExpressionChain(List selectChain){ + if (readId){ + idBinder.buildSelectExpressionChain(prefix, selectChain); + } + for (int i = 0, x = properties.length; i < x; i++) { + properties[i].buildSelectExpressionChain(prefix, selectChain); + } + // recursively continue reading... + for (int i = 0; i < children.length; i++) { + // read each child... and let them set their + // values back to this localBean + children[i].buildSelectExpressionChain(selectChain); + } + } + + /** + * read the properties from the resultSet. + */ + public void load(DbReadContext ctx, Object parentBean) throws SQLException { + + // bean already existing in the persistence context + Object contextBean = null; + + Class localType; + BeanDescriptor localDesc; + IdBinder localIdBinder; + Object localBean; + + if (inheritInfo != null){ + InheritInfo localInfo = inheritInfo.readType(ctx); + if (localInfo == null){ + // the bean must be null + localIdBinder = idBinder; + localBean = null; + localType = null; + localDesc = desc; + } else { + localBean = localInfo.createBean(ctx.isVanillaMode()); + localType = localInfo.getType(); + localIdBinder = localInfo.getIdBinder(); + localDesc = localInfo.getBeanDescriptor(); + } + + } else { + localType = null; + localDesc = desc; + localBean = desc.createBean(ctx.isVanillaMode()); + localIdBinder = idBinder; + } + + Mode queryMode = ctx.getQueryMode(); + + PersistenceContext persistenceContext = ctx.getPersistenceContext(); + + Object id = null; + if (!readId){ + // report type bean... or perhaps excluding the id for SqlSelect? + + } else { + id = localIdBinder.readSet(ctx, localBean); + if (id == null){ + // bean must be null... + localBean = null; + } else { + // check the PersistenceContext to see if the bean already exists + contextBean = persistenceContext.putIfAbsent(id, localBean); + if (contextBean == null){ + // bean just added to the persistenceContext + contextBean = localBean; + } else { + // bean already exists in persistenceContext + if (queryMode.isLoadContextBean()){ + // refresh it anyway (lazy loading for example) + localBean = contextBean; + if (localBean instanceof EntityBean){ + // temporarily turn off interception during load + ((EntityBean)localBean)._ebean_getIntercept().setIntercepting(false); + } + } else { + // ignore the DB data... + localBean = null; + } + } + } + } + + ctx.setCurrentPrefix(prefix, pathMap); + + ctx.propagateState(localBean); + + SqlBeanLoad sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); + + if (inheritInfo == null){ + // normal behaviour with no inheritance + for (int i = 0, x = properties.length; i < x; i++) { + properties[i].load(sqlBeanLoad); + } + + } else { + // take account of inheritance and due to subclassing approach + // need to get a 'local' version of the property + for (int i = 0, x = properties.length; i < x; i++) { + // get a local version of the BeanProperty + BeanProperty p = localDesc.getBeanProperty(properties[i].getName()); + if (p != null){ + p.load(sqlBeanLoad); + } else { + properties[i].loadIgnore(ctx); + } + } + } + + for (int i = 0, x = tableJoins.length; i < x; i++) { + tableJoins[i].load(sqlBeanLoad); + } + + boolean lazyLoadMany = false; + if (localBean == null && queryMode.equals(Mode.LAZYLOAD_MANY)){ + // batch lazy load many into existing contextBean + localBean = contextBean; + lazyLoadMany = true; + } + + // recursively continue reading... + for (int i = 0; i < children.length; i++) { + // read each child... and let them set their + // values back to this localBean + children[i].load(ctx, localBean); + } + + if (lazyLoadMany){ + // special case where we load children + + } else if (localBean != null) { + + ctx.setCurrentPrefix(prefix, pathMap); + if (!ctx.isVanillaMode()){ + // only create lazy loading collection proxies + // when not in vanilla mode + createListProxies(localDesc, ctx, localBean); + } + + localDesc.postLoad(localBean, includedProps); + + if (localBean instanceof EntityBean) { + EntityBeanIntercept ebi = ((EntityBean)localBean)._ebean_getIntercept(); + ebi.setPersistenceContext(persistenceContext); + ebi.setLoadedProps(includedProps); + if (Mode.LAZYLOAD_BEAN.equals(queryMode)) { + // Lazy Load does not reset the dirty state + ebi.setLoadedLazy(); + } else { + // normal bean loading + ebi.setLoaded(); + } + + if (partialObject) { + ctx.register(null, ebi); + } + + if (disableLazyLoad) { + // bean does not have an Id or is SqlSelect based + ebi.setDisableLazyLoad(true); + } + if (ctx.isAutoFetchProfiling()) { + // collect autofetch profiling for this bean... + ctx.profileBean(ebi, prefix); + } + } + + } + if (parentBean != null && contextBean != null) { + // set this back to the parentBean + nodeBeanProp.setValue(parentBean, contextBean); + } + + if (!readId){ + // a bean with no Id (never found in context) + postLoad(ctx, localBean, id); + + } else { + // return the contextBean which is either the localBean + // read from the resultSet and put into the context OR + // the 'matching' bean that already existed in the context + postLoad(ctx, contextBean, id); + } + } + + /** + * Create lazy loading proxies for the Many's except for the one that is + * included in the actual query. + */ + private void createListProxies(BeanDescriptor localDesc, DbReadContext ctx, Object localBean) { + + BeanPropertyAssocMany fetchedMany = ctx.getManyProperty(); + + // load the List/Set/Map proxy objects (deferred fetching of lists) + BeanPropertyAssocMany[] manys = localDesc.propertiesMany(); + for (int i = 0; i < manys.length; i++) { + + if (fetchedMany != null && fetchedMany.equals(manys[i])) { + // this many property is included in the query... + // it is being loaded with real row data (result[1]) + } else { + // create a proxy for the many (deferred fetching) + BeanCollection ref = manys[i].createReferenceIfNull(localBean); + if (ref != null){ + ctx.register(manys[i].getName(), ref); + } + } + } + } + + /** + * Append the property columns to the buffer. + */ + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + + ctx.pushJoin(prefix); + ctx.pushTableAlias(prefix); + + if (nodeBeanProp != null) { + ctx.append(NEW_LINE).append(" "); + } + + if (!subQuery && inheritInfo != null){ + ctx.appendColumn(inheritInfo.getDiscriminatorColumn()); + } + + if (readId) { + appendSelect(ctx, false, idBinder.getProperties()); + } + appendSelect(ctx, subQuery, properties); + appendSelectTableJoins(ctx); + + for (int i = 0; i < children.length; i++) { + // read each child... and let them set their + // values back to this localBean + children[i].appendSelect(ctx, subQuery); + } + + ctx.popTableAlias(); + ctx.popJoin(); + } + + private void appendSelectTableJoins(DbSqlContext ctx) { + + String baseAlias = ctx.getTableAlias(prefix); + + for (int i = 0; i < tableJoins.length; i++) { + TableJoin join = tableJoins[i]; + + String alias = baseAlias+i; + + ctx.pushSecondaryTableAlias(alias); + join.appendSelect(ctx, false); + ctx.popTableAlias(); + } + } + + /** + * Append the properties to the buffer. + */ + private void appendSelect(DbSqlContext ctx, boolean subQuery, BeanProperty[] props) { + + for (int i = 0; i < props.length; i++) { + props[i].appendSelect(ctx, subQuery); + } + } + + + public void appendWhere(DbSqlContext ctx) { + + if (inheritInfo != null) { + if (inheritInfo.isRoot()) { + // at root of hierarchy so don't bother + // adding a where clause because we want + // all the types... + } else { + // restrict to this type and + // sub types of this type. + if (ctx.length() > 0){ + ctx.append(" and"); + } + ctx.append(" ").append(ctx.getTableAlias(prefix)).append(".");//tableAlias + ctx.append(inheritInfo.getWhere()).append(" "); + } + } + if (extraWhere != null){ + if (ctx.length() > 0){ + ctx.append(" and"); + } + String ta = ctx.getTableAlias(prefix); + String ew = StringHelper.replaceString(extraWhere, "${ta}", ta); + ctx.append(" ").append(ew).append(" "); + } + + for (int i = 0; i < children.length; i++) { + // recursively add to the where clause any + // fixed predicates (extraWhere etc) + children[i].appendWhere(ctx); + } + } + + /** + * Append to the FROM clause for this node. + */ + public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + + ctx.pushJoin(prefix); + ctx.pushTableAlias(prefix); + + forceOuterJoin = appendFromBaseTable(ctx, forceOuterJoin); + + for (int i = 0; i < properties.length; i++) { + // usually nothing... except for 1-1 Exported + properties[i].appendFrom(ctx, forceOuterJoin); + } + + for (int i = 0; i < children.length; i++) { + children[i].appendFrom(ctx, forceOuterJoin); + } + + ctx.popTableAlias(); + ctx.popJoin(); + } + + /** + * Join to base table for this node. This includes a join to + * the intersection table if this is a ManyToMany node. + */ + public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) { + + if (nodeBeanProp instanceof BeanPropertyAssocMany){ + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany)nodeBeanProp; + if (manyProp.isManyToMany()){ + + String alias = ctx.getTableAlias(prefix); + String[] split = SplitName.split(prefix); + String parentAlias = ctx.getTableAlias(split[0]); + String alias2 = alias+"z_"; + + TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); + manyToManyJoin.addJoin(forceOuterJoin, parentAlias, alias2, ctx); + + return nodeBeanProp.addJoin(forceOuterJoin, alias2, alias, ctx); + } + + } + + return nodeBeanProp.addJoin(forceOuterJoin, prefix, ctx); + } + + + /** + * Summary description. + */ + public String toString() { + return "SqlTreeNodeBean: " + desc; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java new file mode 100644 index 000000000..4c97d885e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -0,0 +1,126 @@ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * The purpose is to add an extra join to the query. + *

    + * This is used to support the where clause or order by clause that refers + * to properties that are NOT included in the select. To support the where clause + * etc in this case we must add an extra join. + *

    + */ +public class SqlTreeNodeExtraJoin implements SqlTreeNode { + + + final BeanPropertyAssoc assocBeanProperty; + + final String prefix; + + final boolean manyJoin; + + List children; + + public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty) { + this.prefix = prefix; + this.assocBeanProperty = assocBeanProperty; + this.manyJoin = assocBeanProperty instanceof BeanPropertyAssocMany; + } + + + + public void buildSelectExpressionChain(List selectChain) { + // nothing to add + } + + + + /** + * Return true if the extra join is a many join. + *

    + * This means we need to add distinct to the sql query. + *

    + */ + public boolean isManyJoin() { + return manyJoin; + } + + + public String getName() { + return prefix; + } + + public void addChild(SqlTreeNodeExtraJoin child){ + if (children == null){ + children = new ArrayList(); + } + children.add(child); + } + + public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + + boolean manyToMany = false; + + if (assocBeanProperty instanceof BeanPropertyAssocMany){ + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany)assocBeanProperty; + if (manyProp.isManyToMany()){ + + manyToMany = true; + + String alias = ctx.getTableAlias(prefix); + String[] split = SplitName.split(prefix); + String parentAlias = ctx.getTableAlias(split[0]); + String alias2 = alias+"z_"; + + TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); + manyToManyJoin.addJoin(forceOuterJoin, parentAlias, alias2, ctx); + + assocBeanProperty.addJoin(forceOuterJoin, alias2, alias, ctx); + } + } + + if (!manyToMany){ + assocBeanProperty.addJoin(forceOuterJoin, prefix, ctx); + } + + if (children != null){ + + if (manyJoin){ + // make sure all decendants use OUTER JOIN + forceOuterJoin = true; + } + + for (int i = 0; i < children.size(); i++) { + SqlTreeNodeExtraJoin child = children.get(i); + child.appendFrom(ctx, forceOuterJoin); + } + } + } + + /** + * Does nothing. + */ + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + } + + /** + * Does nothing. + */ + public void appendWhere(DbSqlContext ctx) { + } + + /** + * Does nothing. + */ + public void load(DbReadContext ctx, Object parentBean) throws SQLException { + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java new file mode 100644 index 000000000..68e7fe832 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -0,0 +1,41 @@ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; + +public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { + + public SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany prop, SqlTreeProperties props, List myList) { + super(prefix, prop, prop.getTargetDescriptor(), props, myList, true); + } + + @Override + protected void postLoad(DbReadContext cquery, Object loadedBean, Object id) { + + // put the localBean into the manyValue so that it + // is added to the collection/map + cquery.setLoadedManyBean(loadedBean); + } + + @Override + public void load(DbReadContext cquery, Object parentBean) throws SQLException { + // pass in null for parentBean because the localBean + // that is built is added to a collection rather than + // being set to the parentBean directly + super.load(cquery, null); + } + + /** + * Force outer join for everything after the many property. + */ + @Override + public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + super.appendFrom(ctx, true); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java new file mode 100644 index 000000000..5806b491f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Join to Many (or child of a many) to support where clause predicates on many properties. + * + * @author rbygrave + */ +public class SqlTreeNodeManyWhereJoin implements SqlTreeNode { + + private final String parentPrefix; + private final String prefix; + private final BeanPropertyAssoc nodeBeanProp; + private final SqlTreeNode[] children; + + public SqlTreeNodeManyWhereJoin(String prefix, BeanPropertyAssoc prop) { + + this.nodeBeanProp = prop; + this.prefix = prefix; + + String[] split = SplitName.split(prefix); + this.parentPrefix = split[0]; + + List childrenList = new ArrayList(0); + this.children = childrenList.toArray(new SqlTreeNode[childrenList.size()]); + } + + /** + * Append to the FROM clause for this node. + */ + public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) { + + appendFromBaseTable(ctx, forceOuterJoin); + + for (int i = 0; i < children.length; i++) { + children[i].appendFrom(ctx, forceOuterJoin); + } + } + + /** + * Join to base table for this node. This includes a join to the + * intersection table if this is a ManyToMany node. + */ + public void appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) { + + String alias = ctx.getTableAliasManyWhere(prefix); + String parentAlias = ctx.getTableAliasManyWhere(parentPrefix); + + if (nodeBeanProp instanceof BeanPropertyAssocOne){ + nodeBeanProp.addInnerJoin(parentAlias, alias, ctx); + + } else { + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany)nodeBeanProp; + if (!manyProp.isManyToMany()) { + manyProp.addInnerJoin(parentAlias, alias, ctx); + + } else { + String alias2 = alias + "z_"; + + TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); + manyToManyJoin.addInnerJoin(parentAlias, alias2, ctx); + manyProp.addInnerJoin(alias2, alias, ctx); + } + } + } + + public void buildSelectExpressionChain(List selectChain) { + // nothing to add + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + // nothing to do here + } + + public void appendWhere(DbSqlContext ctx) { + // nothing to do here + } + + public void load(DbReadContext ctx, Object parentBean) throws SQLException { + // nothing to do here + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java new file mode 100644 index 000000000..619842d63 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -0,0 +1,55 @@ +package com.avaje.ebeaninternal.server.query; + +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Represents the root node of the Sql Tree. + */ +public final class SqlTreeNodeRoot extends SqlTreeNodeBean { + + private final TableJoin includeJoin; + + /** + * Specify for SqlSelect to include an Id property or not. + */ + public SqlTreeNodeRoot(BeanDescriptor desc, SqlTreeProperties props, List myList, boolean withId, TableJoin includeJoin){ + super(null, null, desc, props, myList, withId); + this.includeJoin = includeJoin; + } + + public SqlTreeNodeRoot(BeanDescriptor desc, SqlTreeProperties props, List myList, boolean withId) { + super(null, null, desc, props, myList, withId); + this.includeJoin = null; + } + + @Override + protected void postLoad(DbReadContext cquery, Object loadedBean, Object id) { + + // set the current bean with id... + cquery.setLoadedBean(loadedBean, id); + } + + /** + * For the root node there is no join type or on clause etc. + */ + @Override + public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) { + + ctx.append(desc.getBaseTable()); + ctx.append(" ").append(ctx.getTableAlias(null)); + + if (includeJoin != null){ + String a1 = ctx.getTableAlias(null); + String a2 = "int_"; // unique alias for intersection join + includeJoin.addJoin(forceOuterJoin, a1, a2, ctx); + } + + return forceOuterJoin; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java new file mode 100644 index 000000000..0cfcbb0b5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java @@ -0,0 +1,107 @@ +/** + * + */ +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * The select properties for a node in the SqlTree. + */ +public class SqlTreeProperties { + + /** + * The included Properties that will be used by EntityBeanIntercept + * to determine lazy loading on partial objects. + */ + Set includedProps; + + /** + * True if this node of the tree should have read only entity beans. + */ + boolean readOnly; + + /** + * set to false if the id field is not included. + */ + boolean includeId = true; + + TableJoin[] tableJoins = new TableJoin[0]; + + /** + * The bean properties in order. + */ + List propsList = new ArrayList(); + + /** + * Maintain a list of property names to detect embedded bean additions. + */ + LinkedHashSet propNames = new LinkedHashSet(); + + public SqlTreeProperties() { + + } + + public boolean containsProperty(String propName){ + return propNames.contains(propName); + } + + public void add(BeanProperty[] props) { + for (BeanProperty beanProperty : props) { + propsList.add(beanProperty); + } + } + + public void add(BeanProperty prop) { + propsList.add(prop); + propNames.add(prop.getName()); + + } + + public BeanProperty[] getProps() { + return propsList.toArray(new BeanProperty[propsList.size()]); + } + + public boolean isIncludeId() { + return includeId; + } + + public void setIncludeId(boolean includeId) { + this.includeId = includeId; + } + + public boolean isPartialObject() { + return includedProps != null; + } + + public Set getIncludedProperties() { + return includedProps; + } + + public void setIncludedProperties(Set includedProps) { + this.includedProps = includedProps; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public TableJoin[] getTableJoins() { + return tableJoins; + } + + public void setTableJoins(TableJoin[] tableJoins) { + this.tableJoins = tableJoins; + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/query/package-info.java new file mode 100644 index 000000000..13328791e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/package-info.java @@ -0,0 +1,4 @@ +/** + * Controls execution of the Orm and Raw Sql queries. + */ +package com.avaje.ebeaninternal.server.query; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java new file mode 100644 index 000000000..6a1b1e0e5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -0,0 +1,1329 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Expression; +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.FetchConfig; +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.FutureList; +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.OrderBy.Property; +import com.avaje.ebean.PagingList; +import com.avaje.ebean.Query; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.QueryListener; +import com.avaje.ebean.QueryResultVisitor; +import com.avaje.ebean.RawSql; +import com.avaje.ebean.bean.BeanCollectionTouched; +import com.avaje.ebean.bean.CallStack; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.ObjectGraphOrigin; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebean.meta.MetaAutoFetchStatistic; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +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.SimpleExpression; +import com.avaje.ebeaninternal.server.query.CancelableQuery; +import com.avaje.ebeaninternal.util.DefaultExpressionList; + +/** + * Default implementation of an Object Relational query. + */ +public class DefaultOrmQuery implements SpiQuery { + + private static final long serialVersionUID = 6838006264714672460L; + + private final Class beanType; + + private transient final EbeanServer server; + + private transient BeanCollectionTouched beanCollectionTouched; + + private transient final ExpressionFactory expressionFactory; + + /** + * Used to add beans to the PersistanceContext prior to query. + */ + private transient ArrayList contextAdditions; + + private transient QueryListener queryListener; + + /** + * 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 transient AutoFetchManager autoFetchManager; + + private transient BeanDescriptor beanDescriptor; + + private boolean cancelled; + + private transient CancelableQuery cancelableQuery; + + /** + * The name of the query. + */ + private String name; + + private UseIndex useIndex; + + private Type type; + + private Mode mode = Mode.NORMAL; + + /** + * Holds query in structured form. + */ + private OrmQueryDetail detail; + + private int maxRows; + + private int firstRow; + + private int totalHits; + + /** + * The where clause from a parsed query string. + */ + private String rawWhereClause; + + private OrderBy orderBy; + + private String loadMode; + private String loadDescription; + + private String generatedSql; + + /** + * Query language version of the query. + */ + private String query; + + private String additionalWhere; + + private String additionalHaving; + + private String lazyLoadProperty; + + private String lazyLoadManyPath; + + /** + * Set to true when we want to return vanilla (not enhanced) objects. + */ + private Boolean vanillaMode; + + /** + * Set to true if you want a DISTINCT query. + */ + private boolean distinct; + + /** + * Set to true if this is a future fetch using background threads. + */ + private boolean futureFetch; + + private List partialIds; + + /** + * The rows after which the fetch continues in a bg thread. + */ + private int backgroundFetchAfter; + + private int timeout = -1; + + /** + * The property used to get the key value for a Map. + */ + private String mapKey; + + /** + * Used for find by id type query. + */ + private Object id; + + /** + * Bind parameters when using the query language. + */ + private BindParams bindParams; + + private DefaultExpressionList whereExpressions; + + private DefaultExpressionList havingExpressions; + + private int bufferFetchSizeHint; + + private boolean usageProfiling = true; + + private boolean loadBeanCache; + private Boolean useBeanCache; + + private Boolean useQueryCache; + + private Boolean readOnly; + + private boolean sqlSelect; + + /** + * Allow for explicit on off or null for default. + */ + private Boolean autoFetch; + + /** + * Allow to fetch a record "for update" which should lock it on read + */ + private boolean forUpdate; + + /** + * Set to true if this query has been tuned by autoFetch. + */ + private boolean autoFetchTuned; + + /** + * The node of the bean or collection that fired lazy loading. Not null if + * profiling is on and this query is for lazy loading. Used to hook back a + * lazy loading query to the "original" query point. + */ + private ObjectGraphNode parentNode; + + /** + * Hash of final query after AutoFetch tuning. + */ + private int queryPlanHash; + + private transient PersistenceContext persistenceContext; + + private ManyWhereJoins manyWhereJoins; + + private RawSql rawSql; + + public DefaultOrmQuery(Class beanType, EbeanServer server, ExpressionFactory expressionFactory, String query) { + this.beanType = beanType; + this.server = server; + this.expressionFactory = expressionFactory; + this.detail = new OrmQueryDetail(); + this.name = ""; + if (query != null){ + setQuery(query); + } + } + + /** + * Additional supply a query which is parsed. + */ + public DefaultOrmQuery(Class beanType, EbeanServer server, ExpressionFactory expressionFactory, + DeployNamedQuery namedQuery) throws PersistenceException { + + this.beanType = beanType; + this.server = server; + this.expressionFactory = expressionFactory; + this.detail = new OrmQueryDetail(); + if (namedQuery == null){ + this.name = ""; + } else { + this.name = namedQuery.getName(); + this.sqlSelect = namedQuery.isSqlSelect(); + if (sqlSelect) { + // potentially with where and having clause... + DRawSqlSelect sqlSelect = namedQuery.getSqlSelect(); + additionalWhere = sqlSelect.getWhereClause(); + additionalHaving = sqlSelect.getHavingClause(); + } else if (namedQuery.isRawSql()){ + rawSql = namedQuery.getRawSql(); + + } else { + // parse the entire query... + setQuery(namedQuery.getQuery()); + } + } + } + + public int getTotalHits() { + return totalHits; + } + + public void setTotalHits(int totalHits) { + this.totalHits = totalHits; + } + + /** + * Set the BeanDescriptor for the root type of this query. + */ + public void setBeanDescriptor(BeanDescriptor beanDescriptor) { + this.beanDescriptor = beanDescriptor; + } + + /** + * Return true if select all properties was used to ensure the + * property invoking a lazy load was included in the query. + */ + public boolean selectAllForLazyLoadProperty() { + if (lazyLoadProperty != null){ + if (!detail.containsProperty(lazyLoadProperty)){ + detail.select("*"); + return true; + } + } + return false; + } + + public RawSql getRawSql() { + return rawSql; + } + + public DefaultOrmQuery setRawSql(RawSql rawSql) { + this.rawSql = rawSql; + return this; + } + + public String getLazyLoadProperty() { + return lazyLoadProperty; + } + + public void setLazyLoadProperty(String lazyLoadProperty) { + this.lazyLoadProperty = lazyLoadProperty; + } + + public String getLazyLoadManyPath() { + return lazyLoadManyPath; + } + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } + + public MetaAutoFetchStatistic getMetaAutoFetchStatistic() { + if (parentNode != null && server != null){ + ObjectGraphOrigin origin = parentNode.getOriginQueryPoint(); + return server.find(MetaAutoFetchStatistic.class, origin.getKey()); + } + return null; + } + + /** + * Return true if the where expressions contains a many property. + */ + public boolean initManyWhereJoins() { + manyWhereJoins = new ManyWhereJoins(); + if (whereExpressions != null){ + whereExpressions.containsMany(beanDescriptor, manyWhereJoins); + } + return !manyWhereJoins.isEmpty(); + } + + public ManyWhereJoins getManyWhereJoins() { + return manyWhereJoins; + } + + public List removeQueryJoins() { + List queryJoins = detail.removeSecondaryQueries(); + if (queryJoins != null){ + if (orderBy != null){ + // remove any orderBy properties that relate to + // paths of the secondary queries + for (int i = 0; i < queryJoins.size(); i++) { + OrmQueryProperties joinPath = queryJoins.get(i); + + // loop through the orderBy properties and + // move any ones related to the query join + List properties = orderBy.getProperties(); + Iterator it = properties.iterator(); + while (it.hasNext()) { + OrderBy.Property property = it.next(); + if (property.getProperty().startsWith(joinPath.getPath())){ + // remove this orderBy segment and + // add it to the secondary join + it.remove(); + joinPath.addSecJoinOrderProperty(property); + } + } + } + } + } + return queryJoins; + } + + public List removeLazyJoins() { + return detail.removeSecondaryLazyQueries(); + } + + public void setLazyLoadManyPath(String lazyLoadManyPath) { + this.lazyLoadManyPath = lazyLoadManyPath; + } + + /** + * Convert any many joins fetch joins to query joins. + */ + public void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch) { + detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch); + } + + /** + * Set the select clause to select the Id property. + */ + public void setSelectId() { + // clear select and fetch joins.. + detail.clear(); + + select(beanDescriptor.getIdBinder().getIdProperty()); + } + + public void convertWhereNaturalKeyToId(Object idValue) { + whereExpressions = new DefaultExpressionList(this, null); + setId(idValue); + } + + public NaturalKeyBindParam getNaturalKeyBindParam() { + NaturalKeyBindParam namedBind = null; + if (bindParams != null){ + namedBind = bindParams.getNaturalKeyBindParam(); + if (namedBind == null) { + return null; + } + } + + if (whereExpressions != null){ + List exprList = whereExpressions.internalList(); + if (exprList.size() > 1){ + return null; + } else if (exprList.size() == 0) { + return namedBind; + } else { + if (namedBind != null){ + return null; + } + SpiExpression se = exprList.get(0); + if (se instanceof SimpleExpression){ + SimpleExpression e = (SimpleExpression)se; + if (e.isOpEquals()){ + return new NaturalKeyBindParam(e.getPropertyName(), e.getValue()); + } + } + } + } + return null; + } + + public DefaultOrmQuery copy() { + // Not including these in the copy: + // contextAdditions + // queryListener + // transactionContext + // autoFetchTuned + // autoFetchQueryPlanHash + // copy.generatedSql + + DefaultOrmQuery copy = new DefaultOrmQuery(beanType, server, expressionFactory, (String)null); + copy.name = name; + copy.includeTableJoin = includeTableJoin; + copy.autoFetchManager = autoFetchManager; + + copy.query = query; + copy.additionalWhere = additionalWhere; + copy.additionalHaving = additionalHaving; + copy.distinct = distinct; + copy.backgroundFetchAfter = backgroundFetchAfter; + copy.timeout = timeout; + copy.mapKey = mapKey; + copy.id = id; + copy.vanillaMode = vanillaMode; + copy.loadBeanCache = loadBeanCache; + copy.useBeanCache = useBeanCache; + copy.useQueryCache = useQueryCache; + copy.readOnly = readOnly; + copy.sqlSelect = sqlSelect; + if (detail != null){ + copy.detail = detail.copy(); + } + + copy.firstRow = firstRow; + copy.maxRows = maxRows; + copy.rawWhereClause = rawWhereClause; + if (orderBy != null){ + copy.orderBy = orderBy.copy(); + } + if (bindParams != null){ + copy.bindParams = bindParams.copy(); + } + if (whereExpressions != null){ + copy.whereExpressions = whereExpressions.copy(copy); + } + if (havingExpressions != null){ + copy.havingExpressions = havingExpressions.copy(copy); + } + copy.usageProfiling = usageProfiling; + copy.autoFetch = autoFetch; + copy.parentNode = parentNode; + copy.forUpdate = forUpdate; + copy.rawSql = rawSql; + copy.rawWhereClause = rawWhereClause; + return copy; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public UseIndex getUseIndex() { + return useIndex; + } + + public DefaultOrmQuery setUseIndex(UseIndex useIndex) { + this.useIndex = useIndex; + return this; + } + + public String getLoadDescription() { + return loadDescription; + } + + public String getLoadMode() { + return loadMode; + } + + public void setLoadDescription(String loadMode, String loadDescription) { + this.loadMode = loadMode; + this.loadDescription = loadDescription; + } + + /** + * Return the TransactionContext. + *

    + * If no TransactionContext is present on the query then the TransactionContext + * from the Transaction is used (transaction scoped persistence context). + *

    + */ + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + /** + * Set an explicit TransactionContext (typically for a refresh query). + *

    + * If no TransactionContext is present on the query then the TransactionContext + * from the Transaction is used (transaction scoped persistence context). + *

    + */ + public void setPersistenceContext(PersistenceContext persistenceContext) { + this.persistenceContext = persistenceContext; + } + + /** + * Return true if the query detail has neither select or joins specified. + */ + public boolean isDetailEmpty() { + return detail.isEmpty(); + } + + public boolean isAutofetchTuned() { + return autoFetchTuned; + } + + public void setAutoFetchTuned(boolean autoFetchTuned) { + this.autoFetchTuned = autoFetchTuned; + } + + public Boolean isAutofetch() { + return sqlSelect ? Boolean.FALSE : autoFetch; + } + + public boolean isForUpdate() { + return forUpdate; + } + + public DefaultOrmQuery setAutoFetch(boolean autoFetch) { + return setAutofetch(autoFetch); + } + + public DefaultOrmQuery setAutofetch(boolean autoFetch) { + this.autoFetch = autoFetch; + return this; + } + + public DefaultOrmQuery setForUpdate(boolean forUpdate) { + this.forUpdate = forUpdate; + return this; + } + + public AutoFetchManager getAutoFetchManager() { + return autoFetchManager; + } + + public void setAutoFetchManager(AutoFetchManager autoFetchManager) { + this.autoFetchManager = autoFetchManager; + } + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + public boolean isUsageProfiling() { + return usageProfiling; + } + + public void setUsageProfiling(boolean usageProfiling) { + this.usageProfiling = usageProfiling; + } + + public void setParentNode(ObjectGraphNode parentNode) { + this.parentNode = parentNode; + } + + public ObjectGraphNode getParentNode() { + return parentNode; + } + + + public ObjectGraphNode setOrigin(CallStack callStack) { + + // create a 'origin' which links this query to the profiling information + ObjectGraphOrigin o = new ObjectGraphOrigin(calculateOriginQueryHash(), callStack, beanType.getName()); + parentNode = new ObjectGraphNode(o, null); + return parentNode; + } + + /** + * Calculate a hash for use in determining the ObjectGraphOrigin. + *

    + * This should be quite a stable hash as most uniqueness is determined + * by the CallStack, so we only use the bean type and overall query type. + *

    + *

    + * This stable hash allows the query to be changed (joins added etc) without + * losing the already collected usage profiling. + *

    + */ + private int calculateOriginQueryHash() { + int hc = beanType.getName().hashCode(); + hc = hc * 31 + (type == null ? 0 : type.ordinal()); + return hc; + } + + /** + * Calculate the query hash for either AutoFetch query tuning or Query Plan caching. + */ + private int calculateHash(BeanQueryRequest request) { + + // exclude bind values and things unrelated to + // the sql being generated + + // must use String name of class as actual class hashCode + // can change between JVM restarts. + int hc = beanType.getName().hashCode(); + + hc = hc * 31 + (type == null ? 0 : type.ordinal()); + hc = hc * 31 + (useIndex == null ? 0 : useIndex.hashCode()); + + hc = hc * 31 + (rawSql == null ? 0 : rawSql.queryHash()); + + hc = hc * 31 + (autoFetchTuned ? 31 : 0); + hc = hc * 31 + (distinct ? 31 : 0); + hc = hc * 31 + (query == null ? 0 : query.hashCode()); + hc = hc * 31 + detail.queryPlanHash(request); + + hc = hc * 31 + (firstRow == 0 ? 0 : firstRow); + hc = hc * 31 + (maxRows == 0 ? 0 : maxRows); + hc = hc * 31 + (orderBy == null ? 0 : orderBy.hash()); + hc = hc * 31 + (rawWhereClause == null ? 0 : rawWhereClause.hashCode()); + + hc = hc * 31 + (additionalWhere == null ? 0 : additionalWhere.hashCode()); + hc = hc * 31 + (additionalHaving == null ? 0 : additionalHaving.hashCode()); + hc = hc * 31 + (mapKey == null ? 0 : mapKey.hashCode()); + hc = hc * 31 + (id == null ? 0 : 1); + + if (bindParams != null) { + hc = hc * 31 + bindParams.getQueryPlanHash(); + } + + if (request == null) { + // for AutoFetch... + hc = hc * 31 + (whereExpressions == null ? 0 : whereExpressions.queryAutoFetchHash()); + hc = hc * 31 + (havingExpressions == null ? 0 : havingExpressions.queryAutoFetchHash()); + + } else { + // for query plan... + hc = hc * 31 + (whereExpressions == null ? 0 : whereExpressions.queryPlanHash(request)); + hc = hc * 31 + (havingExpressions == null ? 0 : havingExpressions.queryPlanHash(request)); + } + + hc = hc * 31 + (forUpdate ? 31 : 0); + + return hc; + } + + /** + * Calculate a hash used by AutoFetch to identify when a query has changed + * (and hence potentially needs a new tuned query plan to be developed). + */ + public int queryAutofetchHash() { + + return calculateHash(null); + } + + /** + * Calculate a hash that should be unique for the generated SQL across a + * given bean type. + *

    + * This can used to enable the caching and reuse of a 'query plan'. + *

    + *

    + * This is calculated AFTER AutoFetch query tuning has occurred. + *

    + */ + public int queryPlanHash(BeanQueryRequest request) { + + queryPlanHash = calculateHash(request); + return queryPlanHash; + } + + /** + * Calculate a hash based on the bind values used in the query. + *

    + * Used with queryPlanHash() to get a unique hash for a query. + *

    + */ + public int queryBindHash() { + int hc = (id == null ? 0 : id.hashCode()); + hc = hc * 31 + (whereExpressions == null ? 0 : whereExpressions.queryBindHash()); + hc = hc * 31 + (havingExpressions == null ? 0 : havingExpressions.queryBindHash()); + hc = hc * 31 + (bindParams == null ? 0 : bindParams.queryBindHash()); + hc = hc * 31 + (contextAdditions == null ? 0 : contextAdditions.hashCode()); + + return hc; + } + + /** + * Return a hash that includes the query plan and bind values. + *

    + * This hash can be used to identify if we have executed the exact same query + * (including bind values) before. + *

    + */ + public int queryHash() { + // calculateQueryPlanHash is called just after potential AutoFetch tuning + // so queryPlanHash is calculated well before this method is called + int hc = queryPlanHash; + hc = hc * 31 + queryBindHash(); + return hc; + } + + /** + * Return the query name. + */ + public String getName() { + return name; + } + + public boolean isSqlSelect() { + return sqlSelect; + } + + public boolean isRawSql() { + return rawSql != null; + } + + /** + * Return any additional where clauses. + */ + public String getAdditionalWhere() { + return additionalWhere; + } + + /** + * Return the timeout. + */ + public int getTimeout() { + return timeout; + } + + /** + * Return any additional having clauses. + */ + public String getAdditionalHaving() { + return additionalHaving; + } + + public boolean hasMaxRowsOrFirstRow() { + return maxRows > 0 || firstRow > 0; + } + + + public boolean isVanillaMode(boolean defaultVanillaMode) { + if (vanillaMode != null) { + return vanillaMode.booleanValue(); + } + return defaultVanillaMode; + } + + public DefaultOrmQuery setVanillaMode(boolean vanillaMode) { + this.vanillaMode = vanillaMode; + return this; + } + + public Boolean isReadOnly() { + return readOnly; + } + + public DefaultOrmQuery setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + return this; + } + + public Boolean isUseBeanCache() { + return useBeanCache; + } + + public boolean isUseQueryCache() { + return Boolean.TRUE.equals(useQueryCache); + } + + public DefaultOrmQuery setUseCache(boolean useBeanCache) { + this.useBeanCache = useBeanCache; + return this; + } + + public DefaultOrmQuery setUseQueryCache(boolean useQueryCache) { + this.useQueryCache = useQueryCache; + return this; + } + + public boolean isLoadBeanCache() { + return loadBeanCache; + } + + public DefaultOrmQuery setLoadBeanCache(boolean loadBeanCache) { + this.loadBeanCache = loadBeanCache; + return this; + } + + public DefaultOrmQuery setTimeout(int secs) { + this.timeout = secs; + return this; + } + + public DefaultOrmQuery setQuery(String queryString) throws PersistenceException { + + this.query = queryString; + + OrmQueryDetailParser parser = new OrmQueryDetailParser(queryString); + parser.parse(); + parser.assign(this); + + return this; + } + + protected void setOrmQueryDetail(OrmQueryDetail detail){ + this.detail = detail; + } + protected void setRawWhereClause(String rawWhereClause){ + this.rawWhereClause = rawWhereClause; + } + + public DefaultOrmQuery setProperties(String columns) { + return select(columns); + } + + public void setDefaultSelectClause(){ + detail.setDefaultSelectClause(beanDescriptor); + } + + public DefaultOrmQuery select(String columns) { + detail.select(columns); + return this; + } + + public DefaultOrmQuery fetch(String property) { + return fetch(property, null, null); + } + + public DefaultOrmQuery fetch(String property, FetchConfig joinConfig) { + return fetch(property, null, joinConfig); + } + + public DefaultOrmQuery fetch(String property, String columns) { + return fetch(property, columns, null); + } + + public DefaultOrmQuery fetch(String property, String columns, FetchConfig config) { + detail.addFetch(property, columns, config); + return this; + } + + public List findIds() { + // a copy of this query is made in the server + // as the query needs to modified (so we modify + // the copy rather than this query instance) + return server.findIds(this, null); + } + + public int findRowCount(){ + // a copy of this query is made in the server + // as the query needs to modified (so we modify + // the copy rather than this query instance) + return server.findRowCount(this, null); + } + + public void findVisit(QueryResultVisitor visitor) { + server.findVisit(this, visitor, null); + } + + public QueryIterator findIterate() { + return server.findIterate(this, null); + } + + public List findList() { + return server.findList(this, null); + } + + public Set findSet() { + return server.findSet(this, null); + } + + public Map findMap() { + return server.findMap(this, null); + } + + @SuppressWarnings("unchecked") + public Map findMap(String keyProperty, Class keyType) { + setMapKey(keyProperty); + return (Map)findMap(); + } + + public T findUnique() { + return server.findUnique(this, null); + } + + public FutureIds findFutureIds() { + return server.findFutureIds(this, null); + } + + public FutureList findFutureList() { + return server.findFutureList(this, null); + } + + public FutureRowCount findFutureRowCount() { + return server.findFutureRowCount(this, null); + } + + public PagingList findPagingList(int pageSize) { + return server.findPagingList(this, null, pageSize); + } + + /** + * Set an ordered bind parameter according to its position. Note that the + * position starts at 1 to be consistent with JDBC PreparedStatement. You + * need to set a parameter value for each ? you have in the query. + */ + public DefaultOrmQuery setParameter(int position, Object value) { + if (bindParams == null) { + bindParams = new BindParams(); + } + bindParams.setParameter(position, value); + return this; + } + + /** + * Set a named bind parameter. Named parameters have a colon to prefix the + * name. + */ + public DefaultOrmQuery setParameter(String name, Object value) { + if (bindParams == null) { + bindParams = new BindParams(); + } + bindParams.setParameter(name, value); + return this; + } + + public OrderBy getOrderBy() { + return orderBy; + } + + /** + * Return the order by clause. + */ + public String getRawWhereClause() { + return rawWhereClause; + } + + public OrderBy orderBy() { + return order(); + } + + public OrderBy order() { + if (orderBy == null){ + orderBy = new OrderBy(this, null); + } + return orderBy; + } + + public DefaultOrmQuery setOrderBy(String orderByClause) { + return order(orderByClause); + } + + public DefaultOrmQuery orderBy(String orderByClause) { + return order(orderByClause); + } + + public DefaultOrmQuery order(String orderByClause) { + if (orderByClause == null || orderByClause.trim().length() == 0){ + this.orderBy = null; + } else { + this.orderBy = new OrderBy(this, orderByClause); + } + return this; + } + + public DefaultOrmQuery setOrderBy(OrderBy orderBy) { + return setOrder(orderBy); + } + + public DefaultOrmQuery setOrder(OrderBy orderBy) { + this.orderBy = orderBy; + if (orderBy != null){ + orderBy.setQuery(this); + } + return this; + } + + + /** + * return true if this query uses DISTINCT. + */ + public boolean isDistinct() { + return distinct; + } + + /** + * Set whether this query uses DISTINCT. + */ + public DefaultOrmQuery setDistinct(boolean isDistinct) { + this.distinct = isDistinct; + return this; + } + + /** + * Return the findListener is one has been set. + */ + public QueryListener getListener() { + return queryListener; + } + + /** + * Set a FindListener. This is designed for large fetches where lots are + * rows are to be processed and instead of returning all the rows they are + * processed one at a time. + *

    + * Note that the returning List Set or Map will be empty. + *

    + */ + public DefaultOrmQuery setListener(QueryListener queryListener) { + this.queryListener = queryListener; + return this; + } + + public Class getBeanType() { + return beanType; + } + + public void setDetail(OrmQueryDetail detail) { + this.detail = detail; + } + + public boolean tuneFetchProperties(OrmQueryDetail tunedDetail) { + return detail.tuneFetchProperties(tunedDetail); + } + + public OrmQueryDetail getDetail() { + return detail; + } + + /** + * Return any beans that should be added to the persistence context prior to + * executing the query. + */ + public final ArrayList getContextAdditions() { + return contextAdditions; + } + + /** + * Add a bean to the context additions. + *

    + * These are added to the persistence context before executing the query. + *

    + */ + public void contextAdd(EntityBean bean) { + if (contextAdditions == null) { + contextAdditions = new ArrayList(); + } + contextAdditions.add(bean); + } + + public String toString() { + return "Query [" + whereExpressions + "]"; + } + + public TableJoin getIncludeTableJoin() { + return includeTableJoin; + } + + public void setIncludeTableJoin(TableJoin includeTableJoin) { + this.includeTableJoin = includeTableJoin; + } + + public int getFirstRow() { + return firstRow; + } + + public DefaultOrmQuery setFirstRow(int firstRow) { + this.firstRow = firstRow; + return this; + } + + public int getMaxRows() { + return maxRows; + } + + public DefaultOrmQuery setMaxRows(int maxRows) { + this.maxRows = maxRows; + return this; + } + + public String getMapKey() { + return mapKey; + } + + public DefaultOrmQuery setMapKey(String mapKey) { + this.mapKey = mapKey; + return this; + } + + public int getBackgroundFetchAfter() { + return backgroundFetchAfter; + } + + public DefaultOrmQuery setBackgroundFetchAfter(int backgroundFetchAfter) { + this.backgroundFetchAfter = backgroundFetchAfter; + return this; + } + + public Object getId() { + return id; + } + + public DefaultOrmQuery setId(Object id) { + this.id = id; + return this; + } + + public BindParams getBindParams() { + return bindParams; + } + + public String getQuery() { + return query; + } + + public DefaultOrmQuery addWhere(String addToWhereClause) { + return where(addToWhereClause); + } + + public DefaultOrmQuery addWhere(Expression expression) { + return where(expression); + } + + public ExpressionList addWhere() { + return where(); + } + + public DefaultOrmQuery where(String addToWhereClause) { + if (additionalWhere == null) { + additionalWhere = addToWhereClause; + } else { + additionalWhere += " " + addToWhereClause; + } + return this; + } + + public DefaultOrmQuery where(Expression expression) { + if (whereExpressions == null) { + whereExpressions = new DefaultExpressionList(this, null); + } + whereExpressions.add(expression); + return this; + } + + public ExpressionList where() { + if (whereExpressions == null) { + whereExpressions = new DefaultExpressionList(this, null); + } + return whereExpressions; + } + + public ExpressionList filterMany(String prop){ + + OrmQueryProperties chunk = detail.getChunk(prop, true); + return chunk.filterMany(this); + } + + + public void setFilterMany(String prop, ExpressionList filterMany){ + if (filterMany != null){ + OrmQueryProperties chunk = detail.getChunk(prop, true); + chunk.setFilterMany((SpiExpressionList)filterMany); + } + } + + public DefaultOrmQuery addHaving(String addToHavingClause) { + return having(addToHavingClause); + } + + public DefaultOrmQuery addHaving(Expression expression) { + return having(expression); + } + + public ExpressionList addHaving() { + return having(); + } + + public DefaultOrmQuery having(String addToHavingClause) { + if (additionalHaving == null) { + additionalHaving = addToHavingClause; + } else { + additionalHaving += " " + addToHavingClause; + } + return this; + } + + public DefaultOrmQuery having(Expression expression) { + if (havingExpressions == null) { + havingExpressions = new DefaultExpressionList(this, null); + } + havingExpressions.add(expression); + return this; + } + + public ExpressionList having() { + if (havingExpressions == null) { + havingExpressions = new DefaultExpressionList(this, null); + } + return havingExpressions; + } + + public SpiExpressionList getHavingExpressions() { + return havingExpressions; + } + + public SpiExpressionList getWhereExpressions() { + return whereExpressions; + } + + /** + * Return true if using background fetching or a queryListener. + */ + public boolean createOwnTransaction() { + if (futureFetch){ + // the future fetches have already created + // their own transaction + return false; + } + if (backgroundFetchAfter > 0 || queryListener != null) { + // run in own transaction as we can't know how long + // the background fetching will continue etc + return true; + } + return false; + } + + public String getGeneratedSql() { + return generatedSql; + } + + public void setGeneratedSql(String generatedSql) { + this.generatedSql = generatedSql; + } + + public Query setBufferFetchSizeHint(int bufferFetchSizeHint){ + this.bufferFetchSizeHint = bufferFetchSizeHint; + return this; + } + + public int getBufferFetchSizeHint() { + return bufferFetchSizeHint; + } + + public void setBeanCollectionTouched(BeanCollectionTouched notify) { + this.beanCollectionTouched = notify; + } + + public BeanCollectionTouched getBeanCollectionTouched() { + return beanCollectionTouched; + } + + public List getIdList() { + return partialIds; + } + + public void setIdList(List partialIds) { + this.partialIds = partialIds; + } + + public boolean isFutureFetch() { + return futureFetch; + } + + public void setFutureFetch(boolean backgroundFetch) { + this.futureFetch = backgroundFetch; + } + + public void setCancelableQuery(CancelableQuery cancelableQuery) { + synchronized (this) { + this.cancelableQuery = cancelableQuery; + } + } + + public void cancel() { + synchronized (this) { + cancelled = true; + if (cancelableQuery != null){ + cancelableQuery.cancel(); + } + } + } + + public boolean isCancelled() { + synchronized (this) { + return cancelled; + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmUpdate.java new file mode 100644 index 000000000..445aa8782 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmUpdate.java @@ -0,0 +1,235 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.querydefn; + +import java.io.Serializable; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiUpdate; +import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate; + +/** + * Default implementation of OrmUpdate. + */ +public final class DefaultOrmUpdate implements SpiUpdate, Serializable { + + private static final long serialVersionUID = -8791423602246515438L; + + + private transient final EbeanServer server; + + private final Class beanType; + + /** + * The name of the update. + */ + private final String name; + + /** + * The parameters used to bind to the sql. + */ + private final BindParams bindParams = new BindParams(); + + /** + * The sql update or delete statement. + */ + private final String updateStatement; + + /** + * Automatically detect the table being modified by this sql. This will + * register this information so that eBean invalidates cached objects if + * required. + */ + private boolean notifyCache = true; + + private int timeout; + + private String generatedSql; + + private final String baseTable; + + private final OrmUpdateType type; + + /** + * Create with a specific server. This means you can use the + * UpdateSql.execute() method. + */ + public DefaultOrmUpdate(Class beanType, EbeanServer server, String baseTable, String updateStatement) { + this.beanType = beanType; + this.server = server; + this.baseTable = baseTable; + this.name = ""; + this.updateStatement = updateStatement; + this.type = deriveType(updateStatement); + + } + + public DefaultOrmUpdate(Class beanType, EbeanServer server, String baseTable, DeployNamedUpdate namedUpdate) { + + this.beanType = beanType; + this.server = server; + this.baseTable = baseTable; + this.name = namedUpdate.getName(); + this.notifyCache = namedUpdate.isNotifyCache(); + + // named updates are always converted to sql as part + // of the initialisation + this.updateStatement = namedUpdate.getSqlUpdateStatement(); + this.type = deriveType(updateStatement); + } + + public DefaultOrmUpdate setTimeout(int secs){ + this.timeout = secs; + return this; + } + + public Class getBeanType() { + return beanType; + } + + /** + * Return the timeout in seconds. + */ + public int getTimeout() { + return timeout; + } + + private SpiUpdate.OrmUpdateType deriveType(String updateStatement) { + + updateStatement = updateStatement.trim(); + int spacepos = updateStatement.indexOf(' '); + if (spacepos == -1){ + return SpiUpdate.OrmUpdateType.UNKNOWN; + + } else { + String firstWord = updateStatement.substring(0, spacepos); + if (firstWord.equalsIgnoreCase("update")){ + return SpiUpdate.OrmUpdateType.UPDATE; + + } else if (firstWord.equalsIgnoreCase("insert")) { + return SpiUpdate.OrmUpdateType.INSERT; + + } else if (firstWord.equalsIgnoreCase("delete")) { + return SpiUpdate.OrmUpdateType.DELETE; + } else { + return SpiUpdate.OrmUpdateType.UNKNOWN; + } + } + } + + public int execute() { + return server.execute(this); + } + + /** + * Set this to false if you don't want eBean to automatically deduce the + * table modification information and process it. + *

    + * Set this to false if you don't want any cache invalidation or text index + * management to occur. You may do this when say you update only one column + * and you know that it is not important for cached objects or text indexes. + *

    + */ + public DefaultOrmUpdate setNotifyCache(boolean notifyCache) { + this.notifyCache = notifyCache; + return this; + } + + /** + * Return true if the cache should be notified so that invalidates + * appropriate objects. + */ + public boolean isNotifyCache() { + return notifyCache; + } + + public String getName() { + return name; + } + + public String getUpdateStatement() { + return updateStatement; + } + + public DefaultOrmUpdate set(int position, Object value) { + bindParams.setParameter(position, value); + return this; + } + + public DefaultOrmUpdate setParameter(int position, Object value) { + bindParams.setParameter(position, value); + return this; + } + + public DefaultOrmUpdate setNull(int position, int jdbcType) { + bindParams.setNullParameter(position, jdbcType); + return this; + } + + public DefaultOrmUpdate setNullParameter(int position, int jdbcType) { + bindParams.setNullParameter(position, jdbcType); + return this; + } + + public DefaultOrmUpdate set(String name, Object value) { + bindParams.setParameter(name, value); + return this; + } + + public DefaultOrmUpdate setParameter(String name, Object param) { + bindParams.setParameter(name, param); + return this; + } + + public DefaultOrmUpdate setNull(String name, int jdbcType) { + bindParams.setNullParameter(name, jdbcType); + return this; + } + + public DefaultOrmUpdate setNullParameter(String name, int jdbcType) { + bindParams.setNullParameter(name, jdbcType); + return this; + } + + /** + * Return the bind parameters. + */ + public BindParams getBindParams() { + return bindParams; + } + + public String getGeneratedSql() { + return generatedSql; + } + + public void setGeneratedSql(String generatedSql) { + this.generatedSql = generatedSql; + } + + public String getBaseTable() { + return baseTable; + } + + public OrmUpdateType getOrmUpdateType() { + return type; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultRelationalQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultRelationalQuery.java new file mode 100644 index 000000000..9dd26d3fa --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultRelationalQuery.java @@ -0,0 +1,229 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.SqlFutureList; +import com.avaje.ebean.SqlQueryListener; +import com.avaje.ebean.SqlRow; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiSqlQuery; + +/** + * Default implementation of SQuery - SQL Query. + */ +public class DefaultRelationalQuery implements SpiSqlQuery { + + private static final long serialVersionUID = -1098305779779591068L; + + private transient EbeanServer server; + + private transient SqlQueryListener queryListener; + + private String query; + + private int firstRow; + + private int maxRows; + + private int timeout; + + private boolean futureFetch; + + private boolean cancelled; + + /** + * For the purposes of cancelling the query. + */ + private transient PreparedStatement pstmt; + + /** + * The rows after which the fetch continues in a bg thread. + */ + private int backgroundFetchAfter; + + private int bufferFetchSizeHint; + + /** + * The property used to get the key value for a Map. + */ + private String mapKey; + + /** + * Bind parameters when using the query language. + */ + private BindParams bindParams = new BindParams(); + + /** + * Additional supply a query detail object. + */ + public DefaultRelationalQuery(EbeanServer server, String query) { + this.server = server; + this.query = query; + } + + public DefaultRelationalQuery setQuery(String query) { + this.query = query; + return this; + } + + public List findList() { + return server.findList(this, null); + } + + public Set findSet() { + return server.findSet(this, null); + } + + public Map findMap() { + return server.findMap(this, null); + } + + public SqlRow findUnique() { + return server.findUnique(this, null); + } + + public SqlFutureList findFutureList() { + return server.findFutureList(this, null); + } + + public DefaultRelationalQuery setParameter(int position, Object value) { + bindParams.setParameter(position, value); + return this; + } + + public DefaultRelationalQuery setParameter(String paramName, Object value) { + bindParams.setParameter(paramName, value); + return this; + } + + /** + * Return the findListener is one has been set. + */ + public SqlQueryListener getListener() { + return queryListener; + } + + /** + * Set a listener. This is designed for large fetches + * where lots are rows are to be processed and instead of + * returning all the rows they are processed one at a time. + *

    + * Note that the returning List Set or Map will be empty. + *

    + */ + public DefaultRelationalQuery setListener(SqlQueryListener queryListener) { + this.queryListener = queryListener; + return this; + } + + public String toString() { + return "SqlQuery ["+query+"]"; + } + + public int getFirstRow() { + return firstRow; + } + + public DefaultRelationalQuery setFirstRow(int firstRow) { + this.firstRow = firstRow; + return this; + } + + public int getMaxRows() { + return maxRows; + } + + public DefaultRelationalQuery setMaxRows(int maxRows) { + this.maxRows = maxRows; + return this; + } + + public String getMapKey() { + return mapKey; + } + + public DefaultRelationalQuery setMapKey(String mapKey) { + this.mapKey = mapKey; + return this; + } + + public int getBackgroundFetchAfter() { + return backgroundFetchAfter; + } + + public DefaultRelationalQuery setBackgroundFetchAfter(int backgroundFetchAfter) { + this.backgroundFetchAfter = backgroundFetchAfter; + return this; + } + + public int getTimeout() { + return timeout; + } + + public DefaultRelationalQuery setTimeout(int secs) { + this.timeout = secs; + return this; + } + + public BindParams getBindParams() { + return bindParams; + } + + public DefaultRelationalQuery setBufferFetchSizeHint(int bufferFetchSizeHint){ + this.bufferFetchSizeHint = bufferFetchSizeHint; + return this; + } + + + public int getBufferFetchSizeHint() { + return bufferFetchSizeHint; + } + + public String getQuery() { + return query; + } + + public boolean isFutureFetch() { + return futureFetch; + } + + public void setFutureFetch(boolean futureFetch) { + this.futureFetch = futureFetch; + } + + public void setPreparedStatement(PreparedStatement pstmt){ + synchronized (this) { + this.pstmt = pstmt; + } + } + + public void cancel() { + synchronized (this) { + this.cancelled = true; + if (pstmt != null){ + try { + pstmt.cancel(); + } catch (SQLException e) { + String msg = "Error cancelling query"; + throw new PersistenceException(msg, e); + } + } + } + } + + public boolean isCancelled() { + synchronized (this) { + return cancelled; + } + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/NaturalKeyBindParam.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/NaturalKeyBindParam.java new file mode 100644 index 000000000..44012c22a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/NaturalKeyBindParam.java @@ -0,0 +1,22 @@ +package com.avaje.ebeaninternal.server.querydefn; + +public class NaturalKeyBindParam { + + private final String name; + + private final Object value; + + public NaturalKeyBindParam(String name, Object value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public Object getValue() { + return value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java new file mode 100644 index 000000000..d525cb077 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -0,0 +1,484 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.FetchConfig; +import com.avaje.ebean.event.BeanQueryRequest; +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.query.SplitName; + +/** + * Represents the internal structure of an Object Relational query. + *

    + * Holds the select() and join() details of a ORM query. + *

    + *

    + * It is worth noting that for autoFetch a "tuned fetch info" builds an instance + * of OrmQueryDetail. Tuning a query is a matter of replacing an instance of + * this class with one that has been tuned with select() and join() set. + *

    + */ +public class OrmQueryDetail implements Serializable { + + private static final long serialVersionUID = -2510486880141461807L; + + /** + * Root level properties. + */ + private OrmQueryProperties baseProps = new OrmQueryProperties(); + + /** + * Contains the fetch/lazy/query joins and their properties. + */ + private LinkedHashMap fetchPaths = new LinkedHashMap(8); + + private LinkedHashSet includes = new LinkedHashSet(8); + + /** + * Return a deep copy of the OrmQueryDetail. + */ + public OrmQueryDetail copy() { + OrmQueryDetail copy = new OrmQueryDetail(); + copy.baseProps = baseProps.copy(); + for (Map.Entry entry : fetchPaths.entrySet()) { + copy.fetchPaths.put(entry.getKey(), entry.getValue().copy()); + } + copy.includes = new LinkedHashSet(includes); + return copy; + } + + /** + * Calculate the hash for the query plan. + */ + public int queryPlanHash(BeanQueryRequest request) { + + int hc = (baseProps == null ? 1 : baseProps.queryPlanHash(request)); + + if (fetchPaths != null) { + for (OrmQueryProperties p : fetchPaths.values()) { + hc = hc * 31 + p.queryPlanHash(request); + } + } + + return hc; + } + + /** + * Return true if equal in terms of autofetch (select and joins). + */ + public boolean isAutoFetchEqual(OrmQueryDetail otherDetail) { + return autofetchPlanHash() == otherDetail.autofetchPlanHash(); + } + + /** + * Calculate the hash for the query plan. + */ + private int autofetchPlanHash() { + + int hc = (baseProps == null ? 1 : baseProps.autofetchPlanHash()); + + if (fetchPaths != null) { + for (OrmQueryProperties p : fetchPaths.values()) { + hc = hc * 31 + p.autofetchPlanHash(); + } + } + + return hc; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + if (baseProps != null) { + sb.append("select ").append(baseProps); + } + if (fetchPaths != null) { + for (OrmQueryProperties join : fetchPaths.values()) { + sb.append(" fetch ").append(join); + } + } + return sb.toString(); + } + + public int hashCode() { + throw new RuntimeException("should not use"); + } + + /** + * set the properties to include on the base / root entity. + */ + public void select(String columns) { + baseProps = new OrmQueryProperties(null, columns); + } + + public boolean containsProperty(String property) { + if (baseProps == null) { + return true; + } else { + return baseProps.isIncluded(property); + } + } + + /** + * Set the base / root query properties. + */ + public void setBase(OrmQueryProperties baseProps) { + this.baseProps = baseProps; + } + + public List removeSecondaryQueries() { + return removeSecondaryQueries(false); + } + + public List removeSecondaryLazyQueries() { + return removeSecondaryQueries(true); + } + + private List removeSecondaryQueries(boolean lazyQuery) { + + ArrayList matchingPaths = new ArrayList(2); + + for (OrmQueryProperties chunk : fetchPaths.values()) { + boolean match = lazyQuery ? chunk.isLazyFetch() : chunk.isQueryFetch(); + if (match) { + matchingPaths.add(chunk.getPath()); + } + } + + if (matchingPaths.size() == 0) { + return null; + } + + // sort into depth order to remove + Collections.sort(matchingPaths); + + // the list of secondary queries + ArrayList props = new ArrayList(2); + + for (int i = 0; i < matchingPaths.size(); i++) { + String path = matchingPaths.get(i); + includes.remove(path); + OrmQueryProperties secQuery = fetchPaths.remove(path); + if (secQuery == null) { + // the path has already been removed by another + // secondary query + + } else { + props.add(secQuery); + + // remove any child properties for this path + Iterator pass2It = fetchPaths.values().iterator(); + while (pass2It.hasNext()) { + OrmQueryProperties pass2Prop = pass2It.next(); + if (secQuery.isChild(pass2Prop)) { + // remove join to secondary query from the main query + // and add to this secondary query + pass2It.remove(); + includes.remove(pass2Prop.getPath()); + secQuery.add(pass2Prop); + } + } + } + } + + // Add the secondary queries as select properties + // to the parent chunk to ensure the foreign keys + // are included in the query + for (int i = 0; i < props.size(); i++) { + String path = props.get(i).getPath(); + // split into parent and property + String[] split = SplitName.split(path); + // add property to parent chunk + OrmQueryProperties chunk = getChunk(split[0], true); + chunk.addSecondaryQueryJoin(split[1]); + } + + return props; + } + + public boolean tuneFetchProperties(OrmQueryDetail tunedDetail) { + + boolean tuned = false; + + OrmQueryProperties tunedRoot = tunedDetail.getChunk(null, false); + if (tunedRoot != null && tunedRoot.hasProperties()) { + tuned = true; + baseProps.setTunedProperties(tunedRoot); + + for (OrmQueryProperties tunedChunk : tunedDetail.fetchPaths.values()) { + OrmQueryProperties chunk = getChunk(tunedChunk.getPath(), false); + if (chunk != null) { + // set the properties to select + chunk.setTunedProperties(tunedChunk); + } else { + // add a missing join + putFetchPath(tunedChunk.copy()); + } + } + } + return tuned; + } + + /** + * Matches a join() method of the query. + */ + public void putFetchPath(OrmQueryProperties chunk) { + String path = chunk.getPath(); + fetchPaths.put(path, chunk); + includes.add(path); + } + + /** + * Remove all joins and properties. + *

    + * Typically for the row count query. + *

    + */ + public void clear() { + includes.clear(); + fetchPaths.clear(); + } + + /** + * Set the fetch properties and configuration for a given path. + * + * @param path + * the property to join + * @param partialProps + * the properties on the join property to include + */ + public OrmQueryProperties addFetch(String path, String partialProps, FetchConfig fetchConfig) { + + OrmQueryProperties chunk = getChunk(path, true); + chunk.setProperties(partialProps); + chunk.setFetchConfig(fetchConfig); + return chunk; + } + + public void sortFetchPaths(BeanDescriptor d) { + + LinkedHashMap sorted = new LinkedHashMap(fetchPaths.size()); + + for (OrmQueryProperties p : fetchPaths.values()) { + sortFetchPaths(d, p, sorted); + } + + fetchPaths = sorted; + } + + private void sortFetchPaths(BeanDescriptor d, OrmQueryProperties p, + LinkedHashMap sorted) { + + String path = p.getPath(); + if (!sorted.containsKey(path)) { + String parentPath = p.getParentPath(); + if (parentPath == null || sorted.containsKey(parentPath)) { + // off root path or parent already ahead in fetch order + sorted.put(path, p); + } else { + OrmQueryProperties parentProp = fetchPaths.get(parentPath); + if (parentProp == null) { + ElPropertyValue el = d.getElGetValue(parentPath); + if (el == null) { + String msg = "Path [" + parentPath + "] not valid from " + d.getFullName(); + throw new PersistenceException(msg); + } + // add a missing parent path just fetching the Id property + BeanPropertyAssoc assocOne = (BeanPropertyAssoc) el.getBeanProperty(); + parentProp = new OrmQueryProperties(parentPath, assocOne.getTargetIdProperty()); + } + if (parentProp != null) { + sortFetchPaths(d, parentProp, sorted); + } + sorted.put(path, p); + } + } + } + + /** + * Convert 'fetch joins' to 'many' properties over to 'query joins'. + */ + public void convertManyFetchJoinsToQueryJoins(BeanDescriptor beanDescriptor, String lazyLoadManyPath, + boolean allowOne, int queryBatch) { + + ArrayList manyChunks = new ArrayList(3); + + // the name of the many fetch property if there is one + String manyFetchProperty = null; + + // flag that is set once the many fetch property is chosen + boolean fetchJoinFirstMany = allowOne; + + sortFetchPaths(beanDescriptor); + + for (String fetchPath : fetchPaths.keySet()) { + ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(fetchPath); + if (elProp.containsManySince(manyFetchProperty)) { + + // this is a join to a *ToMany + OrmQueryProperties chunk = fetchPaths.get(fetchPath); + if (chunk.isFetchJoin() + && !isLazyLoadManyRoot(lazyLoadManyPath, chunk) + && !hasParentSecJoin(lazyLoadManyPath, chunk)) { + // this is a 'fetch join' (included in main query) + if (fetchJoinFirstMany) { + // letting the first one remain a 'fetch join' + fetchJoinFirstMany = false; + manyFetchProperty = fetchPath; + } else { + // convert this one over to a 'query join' + manyChunks.add(chunk); + } + } + } + } + + for (int i = 0; i < manyChunks.size(); i++) { + // convert 'fetch joins' over to 'query joins' + manyChunks.get(i).setQueryFetch(queryBatch, true); + } + } + + /** + * Return true if this is actually the root level of a +query/+lazy loading + * query. + */ + private boolean isLazyLoadManyRoot(String lazyLoadManyPath, OrmQueryProperties chunk) { + if (lazyLoadManyPath != null && lazyLoadManyPath.equals(chunk.getPath())) { + return true; + } + return false; + } + + /** + * If the chunk has a parent that is a query or lazy join. In this case it + * does not need to be converted. + */ + private boolean hasParentSecJoin(String lazyLoadManyPath, OrmQueryProperties chunk) { + OrmQueryProperties parent = getParent(chunk); + if (parent == null) { + return false; + } else { + if (lazyLoadManyPath != null && lazyLoadManyPath.equals(parent.getPath())) { + return false; + } else if (!parent.isFetchJoin()) { + return true; + } else { + return hasParentSecJoin(lazyLoadManyPath, parent); + } + } + } + + /** + * Return the parent chunk. + */ + private OrmQueryProperties getParent(OrmQueryProperties chunk) { + String parentPath = chunk.getParentPath(); + return parentPath == null ? null : fetchPaths.get(parentPath); + } + + /** + * Set any default select clauses for the main bean and any joins that have + * not explicitly defined a select clause. + *

    + * That is this will use FetchType.LAZY to exclude some properties by + * default. + *

    + */ + public void setDefaultSelectClause(BeanDescriptor desc) { + + if (desc.hasDefaultSelectClause() && !hasSelectClause()) { + if (baseProps == null) { + baseProps = new OrmQueryProperties(); + } + baseProps.setDefaultProperties(desc.getDefaultSelectClause(), desc.getDefaultSelectClauseSet()); + } + + for (OrmQueryProperties joinProps : fetchPaths.values()) { + if (!joinProps.hasSelectClause()) { + BeanDescriptor assocDesc = desc.getBeanDescriptor(joinProps.getPath()); + if (assocDesc.hasDefaultSelectClause()) { + // use the default select clause + joinProps.setDefaultProperties(assocDesc.getDefaultSelectClause(), assocDesc.getDefaultSelectClauseSet()); + } + } + } + } + + public boolean hasSelectClause() { + return (baseProps != null && baseProps.hasSelectClause()); + } + + /** + * Return true if the query detail has neither select properties specified + * or any joins defined. + */ + public boolean isEmpty() { + return fetchPaths.isEmpty() && (baseProps == null || !baseProps.hasProperties()); + } + + /** + * Return true if there are no joins. + */ + public boolean isJoinsEmpty() { + return fetchPaths.isEmpty(); + } + + /** + * Add the explicit bean join. + *

    + * This is also used to Exclude the matching property from the parent select + * (aka remove the foreign key) because it is now included in it's on node + * in the SqlTree. + *

    + */ + public void includeBeanJoin(String parentPath, String propertyName) { + OrmQueryProperties parentChunk = getChunk(parentPath, true); + parentChunk.includeBeanJoin(propertyName); + } + + public OrmQueryProperties getChunk(String path, boolean create) { + if (path == null) { + return baseProps; + } + OrmQueryProperties props = fetchPaths.get(path); + if (create && props == null) { + props = new OrmQueryProperties(path); + putFetchPath(props); + return props; + + } else { + return props; + } + } + + /** + * Return true if the property is included. + */ + public boolean includes(String path) { + + OrmQueryProperties chunk = fetchPaths.get(path); + + // may not have fetch properties if just +cache etc + return chunk != null && !chunk.isCache(); + } + + /** + * Return the property includes for this detail. + */ + public HashSet getIncludes() { + return includes; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java new file mode 100644 index 000000000..fff9f9da6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java @@ -0,0 +1,200 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import javax.persistence.PersistenceException; + +/** + * Parses a Object relational query statement into a OrmQueryDetail and + * OrmQueryAttributes. + *

    + * The reason they are split into detail and attributes is that the autoFetch + * feature is used to replace the OrmQueryDetail leaving the attributes + * unchanged. + *

    + */ +public class OrmQueryDetailParser { + + private final OrmQueryDetail detail = new OrmQueryDetail(); + + private int maxRows; + + private int firstRow; + + private String rawWhereClause; + + private String rawOrderBy; + + private final SimpleTextParser parser; + + public OrmQueryDetailParser(String oql) { + this.parser = new SimpleTextParser(oql); + } + + public void parse() throws PersistenceException { + + parser.nextWord(); + processInitial(); + } + + protected void assign(DefaultOrmQuery query) { + query.setOrmQueryDetail(detail); + query.setFirstRow(firstRow); + query.setMaxRows(maxRows); + query.setRawWhereClause(rawWhereClause); + query.order(rawOrderBy); + } + + private void processInitial() { + if (parser.isMatch("find")) { + OrmQueryProperties props = readFindFetch(); + detail.setBase(props); + } else { + process(); + } + while (!parser.isFinished()) { + process(); + } + } + + private boolean isFetch() { + return parser.isMatch("fetch") || parser.isMatch("join"); + } + + private void process() { + if (isFetch()) { + OrmQueryProperties props = readFindFetch(); + detail.putFetchPath(props); + + } else if (parser.isMatch("where")) { + readWhere(); + + } else if (parser.isMatch("order", "by")) { + readOrderBy(); + + } else if (parser.isMatch("limit")) { + readLimit(); + + } else { + throw new PersistenceException("Query expected 'fetch', 'where','order by' or 'limit' keyword but got [" + + parser.getWord() + "] \r " + parser.getOql()); + } + } + + private void readLimit() { + try { + String maxLimit = parser.nextWord(); + maxRows = Integer.parseInt(maxLimit); + + String offsetKeyword = parser.nextWord(); + if (offsetKeyword != null) { + if (!parser.isMatch("offset")) { + throw new PersistenceException("expected offset keyword but got " + parser.getWord()); + } + String firstRowLimit = parser.nextWord(); + firstRow = Integer.parseInt(firstRowLimit); + parser.nextWord(); + } + } catch (NumberFormatException e) { + String msg = "Expected an integer for maxRows or firstRows in limit offset clause"; + throw new PersistenceException(msg, e); + } + } + + private void readOrderBy() { + // read the by + parser.nextWord(); + + StringBuilder sb = new StringBuilder(); + while (parser.nextWord() != null) { + if (parser.isMatch("limit")) { + break; + } else { + String w = parser.getWord(); + if (!w.startsWith("(")) { + sb.append(" "); + } + sb.append(w); + } + } + rawOrderBy = sb.toString().trim(); + + if (!parser.isFinished()) { + readLimit(); + } + } + + private void readWhere() { + + int nextMode = 0; + StringBuilder sb = new StringBuilder(); + while ((parser.nextWord()) != null) { + if (parser.isMatch("order", "by")) { + nextMode = 1; + break; + + } else if (parser.isMatch("limit")) { + nextMode = 2; + break; + + } else { + sb.append(" ").append(parser.getWord()); + } + } + String whereClause = sb.toString().trim(); + if (whereClause.length() > 0) { + rawWhereClause = whereClause; + } + + if (nextMode == 1) { + readOrderBy(); + } else if (nextMode == 2) { + readLimit(); + } + } + + private OrmQueryProperties readFindFetch() { + + boolean readAlias = false; + + String props = null; + String path = parser.nextWord(); + String token = null; + while ((token = parser.nextWord()) != null) { + if (!readAlias && parser.isMatch("as")) { + // next token is alias + parser.nextWord(); + readAlias = true; + + } else if ('(' == token.charAt(0)) { + props = token; + parser.nextWord(); + break; + + } else if (isFindFetchEnd()) { + break; + + } else if (!readAlias) { + readAlias = true; + + } else { + throw new PersistenceException("Expected (props) or new 'fetch' 'where' but got " + token); + } + } + if (props != null) { + props = props.substring(1, props.length() - 1); + } + return new OrmQueryProperties(path, props); + } + + private boolean isFindFetchEnd() { + if (isFetch()) { + return true; + } + if (parser.isMatch("where")) { + return true; + } + if (parser.isMatch("order", "by")) { + return true; + } + return false; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java new file mode 100644 index 000000000..3db79733e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java @@ -0,0 +1,50 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.SqlLimitRequest; +import com.avaje.ebeaninternal.api.SpiQuery; + +public class OrmQueryLimitRequest implements SqlLimitRequest { + + final SpiQuery ormQuery; + final DatabasePlatform dbPlatform; + + final String sql; + + final String sqlOrderBy; + + public OrmQueryLimitRequest(String sql, String sqlOrderBy, SpiQuery ormQuery, DatabasePlatform dbPlatform) { + this.sql = sql; + this.sqlOrderBy = sqlOrderBy; + this.ormQuery = ormQuery; + this.dbPlatform = dbPlatform; + } + + public String getDbOrderBy() { + return sqlOrderBy; + } + + public String getDbSql() { + return sql; + } + + public int getFirstRow() { + return ormQuery.getFirstRow(); + } + + public int getMaxRows() { + return ormQuery.getMaxRows(); + } + + public boolean isDistinct() { + return ormQuery.isDistinct(); + } + + public SpiQuery getOrmQuery() { + return ormQuery; + } + + public DatabasePlatform getDbPlatform() { + return dbPlatform; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java new file mode 100644 index 000000000..a16d10741 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -0,0 +1,642 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.avaje.ebean.ExpressionFactory; +import com.avaje.ebean.FetchConfig; +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.OrderBy.Property; +import com.avaje.ebean.Query; +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.SpiExpressionFactory; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.expression.FilterExprPath; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.avaje.ebeaninternal.util.FilterExpressionList; + +/** + * Represents the Properties of an Object Relational query. + */ +public class OrmQueryProperties implements Serializable { + + private static final long serialVersionUID = -8785582703966455658L; + + private String parentPath; + private String path; + + private String properties; + + private String trimmedProperties; + + /** + * NB: -1 means no +query, 0 means use the default batch size. + */ + private int queryFetchBatch = -1; + private boolean queryFetchAll; + + /** + * NB: -1 means no +lazy, 0 means use the default batch size. + */ + private int lazyFetchBatch = -1; + + private FetchConfig fetchConfig; + + private boolean cache; + + private boolean readOnly; + + /** + * Note this SHOULD be a LinkedHashSet to preserve order of the properties. + * This is to make using SqlSelect easier with predictable property/column + * ordering. + */ + private Set included; + + /** + * Included bean joins. + */ + private Set includedBeanJoin; + + /** + * Add these properties to the select so that the foreign key columns are + * included in the query. + */ + private Set secondaryQueryJoins; + + private List secondaryChildren; + + /** + * OrderBy properties that where on the main query + * but moved here as they relate to this (query join). + */ + @SuppressWarnings("rawtypes") + private OrderBy orderBy; + + /** + * A filter that can be applied to the fetch of this path in the object graph. + */ + @SuppressWarnings("rawtypes") + private SpiExpressionList filterMany; + + /** + * Construct with a given path (null == root path). + */ + public OrmQueryProperties(String path) { + this.path = path; + this.parentPath = SplitName.parent(path); + } + + public OrmQueryProperties() { + this(null); + } + + /** + * Used by query language parser. + */ + public OrmQueryProperties(String path, String properties) { + this(path); + setProperties(properties); + } + + /** + * Move a OrderBy.Property from the main query to this query join. + */ + @SuppressWarnings("rawtypes") + public void addSecJoinOrderProperty(OrderBy.Property orderProp) { + if (orderBy == null){ + orderBy = new OrderBy(); + } + orderBy.add(orderProp); + } + + /** + * Set the Fetch configuration options for this path. + */ + public void setFetchConfig(FetchConfig fetchConfig) { + if (fetchConfig != null) { + this.fetchConfig = fetchConfig; + lazyFetchBatch = fetchConfig.getLazyBatchSize(); + queryFetchBatch = fetchConfig.getQueryBatchSize(); + queryFetchAll = fetchConfig.isQueryAll(); + } + } + + public FetchConfig getFetchConfig() { + return fetchConfig; + } + + /** + * Set the comma delimited properties to fetch for this path. + *

    + * This can include the +query and +lazy type hints. + *

    + */ + public void setProperties(String properties) { + this.properties = properties; + this.trimmedProperties = properties; + parseProperties(); + + if (!isAllProperties()) { + Set parsed = parseIncluded(trimmedProperties); + if (parsed.contains("*")){ + this.included = null; + } else { + this.included = parsed; + } + } else { + this.included = null; + } + } + + private boolean isAllProperties() { + return (trimmedProperties == null) || (trimmedProperties.length() == 0) || "*".equals(trimmedProperties); + } + + /** + * Return the expressions used to filter on this path. + * This should be a many path to use this method. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public SpiExpressionList filterMany(Query rootQuery) { + if (filterMany == null){ + FilterExprPath exprPath = new FilterExprPath(path); + SpiExpressionFactory queryEf = (SpiExpressionFactory)rootQuery.getExpressionFactory(); + ExpressionFactory filterEf = queryEf.createExpressionFactory(exprPath); + filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery); + // by default we need to make this a 'query join' now + queryFetchAll = true; + queryFetchBatch = 100; + lazyFetchBatch = 100; + } + return filterMany; + } + + /** + * Return the filterMany expression list (can be null). + */ + public SpiExpressionList getFilterManyTrimPath(int trimPath) { + if (filterMany == null){ + return null; + } + filterMany.trimPath(trimPath); + return filterMany; + } + + /** + * Return the filterMany expression list (can be null). + */ + public SpiExpressionList getFilterMany() { + return filterMany; + } + + /** + * Set the filterMany expression list. + */ + public void setFilterMany(SpiExpressionList filterMany) { + this.filterMany = filterMany; + } + + /** + * Set the properties from deployment default FetchTypes. + */ + public void setDefaultProperties(String properties, Set included) { + this.properties = properties; + this.trimmedProperties = properties; + this.included = included; + } + + /** + * Set the properties from a matching autofetch tuned properties. + */ + public void setTunedProperties(OrmQueryProperties tunedProperties) { + this.properties = tunedProperties.properties; + this.trimmedProperties = tunedProperties.trimmedProperties; + this.included = tunedProperties.included; + } + + /** + * Define the select and joins for this query. + */ + @SuppressWarnings("unchecked") + public void configureBeanQuery(SpiQuery query) { + + if (trimmedProperties != null && trimmedProperties.length() > 0) { + query.select(trimmedProperties); + if (filterMany != null){ + query.setFilterMany(path, filterMany); + } + } + + if (secondaryChildren != null) { + int trimPath = path.length() + 1; + for (int i = 0; i < secondaryChildren.size(); i++) { + OrmQueryProperties p = secondaryChildren.get(i); + String path = p.getPath(); + path = path.substring(trimPath); + query.fetch(path, p.getProperties(), p.getFetchConfig()); + query.setFilterMany(path, p.getFilterManyTrimPath(trimPath)); + } + } + + if (orderBy != null){ + List orderByProps = orderBy.getProperties(); + for (int i = 0; i < orderByProps.size(); i++) { + orderByProps.get(i).trim(path); + } + query.setOrder(orderBy); + } + } + + /** + * Define the select and joins for this query. + */ + @SuppressWarnings("unchecked") + public void configureManyQuery(SpiQuery query) { + + if (trimmedProperties != null && trimmedProperties.length() > 0) { + query.fetch(query.getLazyLoadManyPath(), trimmedProperties); + } + if (filterMany != null){ + query.setFilterMany(path, filterMany); + } + if (secondaryChildren != null) { + + int trimlen = path.length() - query.getLazyLoadManyPath().length(); + + for (int i = 0; i < secondaryChildren.size(); i++) { + OrmQueryProperties p = secondaryChildren.get(i); + String path = p.getPath(); + path = path.substring(trimlen); + query.fetch(path, p.getProperties(), p.getFetchConfig()); + query.setFilterMany(path, p.getFilterManyTrimPath(trimlen)); + } + } + + if (orderBy != null){ + query.setOrder(orderBy); + } + } + + /** + * Creates a copy of the OrmQueryProperties. + */ + public OrmQueryProperties copy() { + OrmQueryProperties copy = new OrmQueryProperties(); + copy.parentPath = parentPath; + copy.path = path; + copy.properties = properties; + copy.cache = cache; + copy.readOnly = readOnly; + copy.queryFetchAll = queryFetchAll; + copy.queryFetchBatch = queryFetchBatch; + copy.lazyFetchBatch = lazyFetchBatch; + copy.filterMany = filterMany; + if (included != null) { + copy.included = new HashSet(included); + } + if (includedBeanJoin != null) { + copy.includedBeanJoin = new HashSet(includedBeanJoin); + } + return copy; + } + + public boolean hasSelectClause() { + if ("*".equals(trimmedProperties)) { + // explicitly selected all properties + return true; + } + // explicitly selected some properties + return included != null; + } + + public String toString() { + String s = ""; + if (path != null) { + s += path + " "; + } + if (properties != null) { + s += "(" + properties + ") "; + } else if (included != null) { + s += "(" + included + ") "; + } + return s; + } + + public boolean isChild(OrmQueryProperties possibleChild) { + return possibleChild.getPath().startsWith(path + "."); + } + + /** + * For secondary queries add a child element. + */ + public void add(OrmQueryProperties child) { + if (secondaryChildren == null) { + secondaryChildren = new ArrayList(); + } + secondaryChildren.add(child); + } + + public int autofetchPlanHash() { + + int hc = (path != null ? path.hashCode() : 1); + if (properties != null){ + hc = hc * 31 + properties.hashCode(); + } else { + hc = hc * 31 + (included != null ? included.hashCode() : 1); + } + + return hc; + } + + /** + * Calculate the query plan hash. + */ + @SuppressWarnings("unchecked") + public int queryPlanHash(BeanQueryRequest request) { + + int hc = (path != null ? path.hashCode() : 1); + if (properties != null){ + hc = hc * 31 + properties.hashCode(); + } else { + hc = hc * 31 + (included != null ? included.hashCode() : 1); + } + hc = hc * 31 + (filterMany != null ? filterMany.queryPlanHash(request) : 1); + + return hc; + } + + public String getProperties() { + return properties; + } + + /** + * Return true if this has properties. + */ + public boolean hasProperties() { + return properties != null || included != null; + } + + /** + * Return true if this property is included as a bean join. + *

    + * If a property is included as a bean join then it should not be included + * as a reference/proxy to avoid duplication. + *

    + */ + public boolean isIncludedBeanJoin(String propertyName) { + if (includedBeanJoin == null) { + return false; + } else { + return includedBeanJoin.contains(propertyName); + } + } + + /** + * Add a bean join property. + */ + public void includeBeanJoin(String propertyName) { + if (includedBeanJoin == null) { + includedBeanJoin = new HashSet(); + } + includedBeanJoin.add(propertyName); + } + + public boolean allProperties() { + return included == null; + } + + /** + * This excludes the bean joined properties. + *

    + * This is because bean joins will have there own node in the SqlTree. + *

    + */ + public Iterator getSelectProperties() { + + if (secondaryQueryJoins == null) { + return included.iterator(); + } + + LinkedHashSet temp = new LinkedHashSet(secondaryQueryJoins.size() + included.size()); + temp.addAll(included); + temp.addAll(secondaryQueryJoins); + return temp.iterator(); + } + + public void addSecondaryQueryJoin(String property) { + if (secondaryQueryJoins == null) { + secondaryQueryJoins = new HashSet(4); + } + secondaryQueryJoins.add(property); + } + + /** + * Return all the properties including the bean joins. This is the set that + * will be used by EntityBeanIntercept to determine if a property needs to + * be lazy loaded. + */ + public Set getAllIncludedProperties() { + + if (included == null) { + return null; + } + + if (includedBeanJoin == null && secondaryQueryJoins == null) { + return new LinkedHashSet(included); + } + + LinkedHashSet s = new LinkedHashSet(2 * (included.size() + 5)); + if (included != null) { + s.addAll(included); + } + if (includedBeanJoin != null) { + s.addAll(includedBeanJoin); + } + if (secondaryQueryJoins != null) { + s.addAll(secondaryQueryJoins); + } + return s; + } + + public boolean isIncluded(String propName) { + + if (includedBeanJoin != null && includedBeanJoin.contains(propName)) { + return false; + } + if (included == null) { + // all properties included + return true; + } + return included.contains(propName); + } + + /** + * Used to convert this join to a query join. + * + * @param queryJoinBatch + * where -1 means not a query join and 0 means use the default + * batch size. + */ + public OrmQueryProperties setQueryFetchBatch(int queryFetchBatch) { + this.queryFetchBatch = queryFetchBatch; + return this; + } + + public OrmQueryProperties setQueryFetchAll(boolean queryFetchAll) { + this.queryFetchAll = queryFetchAll; + return this; + } + + public OrmQueryProperties setQueryFetch(int batch, boolean queryFetchAll) { + this.queryFetchBatch = batch; + this.queryFetchAll = queryFetchAll; + return this; + } + + /** + * Set the lazy loading batch size. + */ + public OrmQueryProperties setLazyFetchBatch(int lazyFetchBatch) { + this.lazyFetchBatch = lazyFetchBatch; + return this; + } + + public boolean isFetchJoin() { + return !isQueryFetch() && !isLazyFetch(); + } + + public boolean isQueryFetch() { + return queryFetchBatch > -1; + } + + public int getQueryFetchBatch() { + return queryFetchBatch; + } + + public boolean isQueryFetchAll() { + return queryFetchAll; + } + + public boolean isLazyFetch() { + return lazyFetchBatch > -1; + } + + public int getLazyFetchBatch() { + return lazyFetchBatch; + } + + public boolean isReadOnly() { + return readOnly; + } + + public boolean isCache() { + return cache; + } + + public String getParentPath() { + return parentPath; + } + + public String getPath() { + return path; + } + + private void parseProperties() { + if (trimmedProperties == null) { + return; + } + int pos = trimmedProperties.indexOf("+readonly"); + if (pos > -1) { + trimmedProperties = StringHelper.replaceString(trimmedProperties, "+readonly", ""); + this.readOnly = true; + } + pos = trimmedProperties.indexOf("+cache"); + if (pos > -1) { + trimmedProperties = StringHelper.replaceString(trimmedProperties, "+cache", ""); + this.cache = true; + } + pos = trimmedProperties.indexOf("+query"); + if (pos > -1) { + queryFetchBatch = parseBatchHint(pos, "+query"); + } + pos = trimmedProperties.indexOf("+lazy"); + if (pos > -1) { + lazyFetchBatch = parseBatchHint(pos, "+lazy"); + } + + trimmedProperties = trimmedProperties.trim(); + while (trimmedProperties.startsWith(",")) { + trimmedProperties = trimmedProperties.substring(1).trim(); + } + } + + private int parseBatchHint(int pos, String option) { + + int startPos = pos + option.length(); + + int endPos = findEndPos(startPos, trimmedProperties); + if (endPos == -1) { + trimmedProperties = StringHelper.replaceString(trimmedProperties, option, ""); + return 0; + + } else { + + String batchParam = trimmedProperties.substring(startPos + 1, endPos); + + if (endPos + 1 >= trimmedProperties.length()) { + trimmedProperties = trimmedProperties.substring(0, pos); + } else { + trimmedProperties = trimmedProperties.substring(0, pos) + trimmedProperties.substring(endPos + 1); + } + return Integer.parseInt(batchParam); + } + } + + private int findEndPos(int pos, String props) { + + if (pos < props.length()) { + if (props.charAt(pos) == '(') { + int endPara = props.indexOf(')', pos + 1); + if (endPara == -1) { + String m = "Error could not find ')' in " + props + " after position " + pos; + throw new RuntimeException(m); + } + return endPara; + } + } + return -1; + } + + /** + * Parse the include separating by comma or semicolon. + */ + private static Set parseIncluded(String rawList) { + + String[] res = rawList.split(","); + + LinkedHashSet set = new LinkedHashSet(res.length + 3); + + String temp = null; + for (int i = 0; i < res.length; i++) { + temp = res[i].trim(); + if (temp.length() > 0) { + set.add(temp); + } + } + + if (set.isEmpty()){ + return null; + } + + return Collections.unmodifiableSet(set); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java new file mode 100644 index 000000000..dd1bffaea --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/SimpleTextParser.java @@ -0,0 +1,177 @@ +package com.avaje.ebeaninternal.server.querydefn; + +public class SimpleTextParser { + + private final String oql; + private final char[] chars; + private final int eof; + + private int pos; + private String word; + private String lowerWord; + + private int openParenthesisCount; + + public SimpleTextParser(String oql) { + this.oql = oql; + this.chars = oql.toCharArray(); + this.eof = oql.length(); + } + + public int getPos() { + return pos; + } + + public String getOql() { + return oql; + } + + public String getWord() { + return word; + } + + public String peekNextWord() { + int origPos = pos; + String nw = nextWordInternal(); + pos = origPos; + return nw; + } + + /** + * Match the current and the next word. + */ + public boolean isMatch(String lowerMatch, String nextWordMatch) { + + if (isMatch(lowerMatch)) { + String nw = peekNextWord(); + if (nw != null) { + nw = nw.toLowerCase(); + return nw.equals(nextWordMatch); + } + } + return false; + } + + public boolean isFinished() { + return word == null; + } + + public int findWordLower(String lowerMatch, int afterPos) { + this.pos = afterPos; + return findWordLower(lowerMatch); + } + + public int findWordLower(String lowerMatch) { + do { + if (nextWord() == null) { + return -1; + } + if (lowerMatch.equals(lowerWord)) { + return pos - lowerWord.length(); + } + } while (true); + } + + /** + * Match the current word. + */ + public boolean isMatch(String lowerMatch) { + return lowerMatch.equals(lowerWord); + } + + public String nextWord() { + word = nextWordInternal(); + if (word != null) { + lowerWord = word.toLowerCase(); + } + return word; + } + + private String nextWordInternal() { + trimLeadingWhitespace(); + if (pos >= eof) { + return null; + } + int start = pos; + if (chars[pos] == '(') { + moveToClose(); + } else { + moveToEndOfWord(); + } + return oql.substring(start, pos); + } + + private void moveToClose() { + + pos++; + openParenthesisCount = 0; + + for (; pos < eof; pos++) { + char c = chars[pos]; + if (c == '(') { + // count nested parenthesis + openParenthesisCount++; + + } else if (c == ')') { + if (openParenthesisCount > 0) { + // still in nested parenthesis + --openParenthesisCount; + } else { + // we have found the end + pos++; + return; + } + } + } + } + + private void moveToEndOfWord() { + char c = chars[pos]; + boolean isOperator = isOperator(c); + for (; pos < eof; pos++) { + c = chars[pos]; + if (isWordTerminator(c, isOperator)) { + return; + } + } + } + + private boolean isWordTerminator(char c, boolean isOperator) { + if (Character.isWhitespace(c)) { + return true; + } + if (isOperator(c)) { + return !isOperator; + } + if (c == '('){ + return true; + } + + return isOperator; + } + + private boolean isOperator(char c) { + switch (c) { + case '<': + return true; + case '>': + return true; + case '=': + return true; + case '!': + return true; + + default: + return false; + } + } + + private void trimLeadingWhitespace() { + for (; pos < eof; pos++) { + char c = chars[pos]; + if (!Character.isWhitespace(c)) { + break; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/package-info.java new file mode 100644 index 000000000..9bdbc5a64 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/package-info.java @@ -0,0 +1,4 @@ +/** + * Objects to define the ORM query itself. + */ +package com.avaje.ebeaninternal.server.querydefn; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflect.java b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflect.java new file mode 100644 index 000000000..52fd95a65 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflect.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.reflect; + +/** + * Provides getter setter and construction methods for beans. + *

    + * This enables the implementation to use standard reflection or + * code generation. + *

    + */ +public interface BeanReflect { + + /** + * Create an EntityBean for this type. + */ + public Object createEntityBean(); + + /** + * Create a plain vanilla bean for this type. + */ + public Object createVanillaBean(); + + public boolean isVanillaOnly(); + + /** + * Return the getter for a given bean property. + */ + public BeanReflectGetter getGetter(String name); + + /** + * Return the setter for a given bean property. + */ + public BeanReflectSetter getSetter(String name); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectFactory.java b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectFactory.java new file mode 100644 index 000000000..747fdff31 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectFactory.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.reflect; + + +/** + * Creates BeanReflect object used to provide getter setter and construction + * for the beans. + */ +public interface BeanReflectFactory { + + /** + * Create the BeanReflect for the given plain bean and its EntityBean equivalent. + */ + public BeanReflect create(Class vanillaType, Class entityBeanType); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectGetter.java b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectGetter.java new file mode 100644 index 000000000..314593078 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectGetter.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.reflect; + +/** + * The getter implementation for a given bean property. + */ +public interface BeanReflectGetter { + + /** + * Return the value of a given bean property. + */ + public Object get(Object bean); + + public Object getIntercept(Object bean); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectSetter.java b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectSetter.java new file mode 100644 index 000000000..f9cdca051 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/reflect/BeanReflectSetter.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.reflect; + +/** + * The setter for a given bean property. + */ +public interface BeanReflectSetter { + + /** + * Set the property value of a bean. + */ + public void set(Object bean, Object value); + + /** + * Set the property value of a bean with interception checks. + *

    + * This could invoke lazy loading and or oldValues creation. + *

    + */ + public void setIntercept(Object bean, Object value); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflect.java b/src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflect.java new file mode 100644 index 000000000..cc41b2c8b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflect.java @@ -0,0 +1,188 @@ +package com.avaje.ebeaninternal.server.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; + +/** + * A BeanReflect implementation based on the enhancement that creates EntityBean + * implementations. + *

    + * That is, based on the fact that instances of the class passed in implement + * the EntityBean interface. + *

    + */ +public final class EnhanceBeanReflect implements BeanReflect { + + private static final Object[] constuctorArgs = new Object[0]; + + private final Class clazz; + private final EntityBean entityBean; + private final Constructor constructor; + private final Constructor vanillaConstructor; + private final boolean hasNewInstanceMethod; + private final boolean vanillaOnly; + + public EnhanceBeanReflect(Class vanillaType, Class clazz) { + try { + this.clazz = clazz; + if (Modifier.isAbstract(clazz.getModifiers())) { + this.entityBean = null; + this.constructor = null; + this.vanillaConstructor = null; + this.hasNewInstanceMethod = false; + this.vanillaOnly = false; + } else { + this.vanillaConstructor = defaultConstructor(vanillaType); + this.constructor = defaultConstructor(clazz); + + Object newInstance = clazz.newInstance(); + if (newInstance instanceof EntityBean){ + this.entityBean = (EntityBean)newInstance; + this.vanillaOnly = false; + this.hasNewInstanceMethod = hasNewInstanceMethod(clazz); + } else { + // probably an XmlElement + this.entityBean = null; + this.vanillaOnly = true; + this.hasNewInstanceMethod = false; + } + } + } catch (InstantiationException e) { + throw new PersistenceException(e); + } catch (IllegalAccessException e) { + throw new PersistenceException(e); + } + } + + private Constructor defaultConstructor(Class cls) { + try { + Class[] params = new Class[0]; + return cls.getDeclaredConstructor(params); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private boolean hasNewInstanceMethod(Class clazz) { + Class[] params = new Class[0]; + try { + Method method = clazz.getMethod("_ebean_newInstance", params); + if (method == null){ + return false; + } + try { + Object o = constructor.newInstance(constuctorArgs); + method.invoke(o, new Object[0]); + return true; + + } catch (AbstractMethodError e){ + return false; + + } catch (InvocationTargetException e){ + return false; + + } catch (Exception e) { + throw new RuntimeException("Unexpected? ", e); + } + } catch (SecurityException e) { + return false; + } catch (NoSuchMethodException e) { + return false; + } + } + + + + public boolean isVanillaOnly() { + return vanillaOnly; + } + + public Object createEntityBean() { + if (hasNewInstanceMethod){ + return entityBean._ebean_newInstance(); + } else { + try { + return constructor.newInstance(constuctorArgs); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + } + + public Object createVanillaBean() { + try { + return vanillaConstructor.newInstance(constuctorArgs); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private int getFieldIndex(String fieldName) { + if (entityBean == null){ + throw new RuntimeException("Trying to get fieldName on abstract class "+clazz); + } + String[] fields = entityBean._ebean_getFieldNames(); + for (int i = 0; i < fields.length; i++) { + if (fieldName.equals(fields[i])) { + return i; + } + } + String fieldList = Arrays.toString(fields); + String msg = "field [" + fieldName + "] not found in [" + clazz.getName() + "]" + fieldList; + throw new IllegalArgumentException(msg); + } + + public BeanReflectGetter getGetter(String name) { + int i = getFieldIndex(name); + return new Getter(i, entityBean); + } + + public BeanReflectSetter getSetter(String name) { + int i = getFieldIndex(name); + return new Setter(i, entityBean); + } + + static final class Getter implements BeanReflectGetter { + private final int fieldIndex; + private final EntityBean entityBean; + + Getter(int fieldIndex, EntityBean entityBean) { + this.fieldIndex = fieldIndex; + this.entityBean = entityBean; + } + + public Object get(Object bean) { + return entityBean._ebean_getField(fieldIndex, bean); + } + + public Object getIntercept(Object bean) { + return entityBean._ebean_getFieldIntercept(fieldIndex, bean); + } + } + + static final class Setter implements BeanReflectSetter { + private final int fieldIndex; + private final EntityBean entityBean; + + Setter(int fieldIndex, EntityBean entityBean) { + this.fieldIndex = fieldIndex; + this.entityBean = entityBean; + } + + public void set(Object bean, Object value) { + entityBean._ebean_setField(fieldIndex, bean, value); + } + + public void setIntercept(Object bean, Object value) { + entityBean._ebean_setFieldIntercept(fieldIndex, bean, value); + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflectFactory.java b/src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflectFactory.java new file mode 100644 index 000000000..87037f84b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/reflect/EnhanceBeanReflectFactory.java @@ -0,0 +1,14 @@ +package com.avaje.ebeaninternal.server.reflect; + +/** + * Creates a BeanReflectFactory based on the enhancement that + * creates EntityBean implementations. + */ +public final class EnhanceBeanReflectFactory implements BeanReflectFactory { + + public BeanReflect create(Class vanillaType, Class entityBeanType) { + return new EnhanceBeanReflect(vanillaType, entityBeanType); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/reflect/package.html b/src/main/java/com/avaje/ebeaninternal/server/reflect/package.html new file mode 100644 index 000000000..4c4321727 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/reflect/package.html @@ -0,0 +1,15 @@ + + + + Bean Reflection + + +Bean reflection Implementation + +

    +Abstracts the bean property getter and setter methods. +Allows Code generation or reflection to be used. +

    + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManager.java b/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManager.java new file mode 100644 index 000000000..e738a7dcb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManager.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.resource; + +import java.io.File; + +import com.avaje.ebeaninternal.server.lib.resource.ResourceSource; + +/** + * The ResourceManager implementation. + */ +public class ResourceManager { + + final ResourceSource resourceSource; + + final File autofetchDir; + + public ResourceManager(ResourceSource resourceSource, File autofetchDir) { + this.resourceSource = resourceSource; + this.autofetchDir = autofetchDir; + } + + public ResourceSource getResourceSource() { + return resourceSource; + } + + public File getAutofetchDirectory() { + return autofetchDir; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManagerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManagerFactory.java new file mode 100644 index 000000000..d3fe2c691 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/resource/ResourceManagerFactory.java @@ -0,0 +1,141 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.resource; + +import java.io.File; +import java.util.logging.Logger; + +import javax.servlet.ServletContext; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.lib.resource.DirectoryFinder; +import com.avaje.ebeaninternal.server.lib.resource.FileResourceSource; +import com.avaje.ebeaninternal.server.lib.resource.ResourceSource; +import com.avaje.ebeaninternal.server.lib.resource.UrlResourceSource; +import com.avaje.ebeaninternal.server.lib.util.NotFoundException; + +/** + * Creates a ResourceManager for a server depending on the avaje.properties. + *

    + * This can use URL based resource loading for web applications or file based + * otherwise. + *

    + */ +public class ResourceManagerFactory { + + private static final Logger logger = Logger.getLogger(ResourceManagerFactory.class.getName()); + + /** + * Construct with the properties for a server. + */ + public ResourceManagerFactory() { + } + + /** + * Create the resource manager given the properties for this server. + */ + public static ResourceManager createResourceManager(ServerConfig serverConfig) { + + ResourceSource resourceSource = createResourceSource(serverConfig); + File autofetchDir = getAutofetchDir(serverConfig, resourceSource); + + return new ResourceManager(resourceSource, autofetchDir); + } + + + /** + * Return the directory that autofetch file goes into. + */ + protected static File getAutofetchDir(ServerConfig serverConfig, ResourceSource resourceSource) { + + String dir = null; + if (serverConfig.getAutofetchConfig() != null) { + dir = serverConfig.getAutofetchConfig().getLogDirectoryWithEval(); + } + if (dir != null) { + return new File(dir); + } + + String realPath = resourceSource.getRealPath(); + if (realPath != null) { + return new File(realPath); + + } else { + throw new RuntimeException("No autofetch directory set?"); + } + } + + /** + * Return the resource loader for external sql files. + *

    + * This can be url based (for webapps) or otherwise file based. + *

    + */ + protected static ResourceSource createResourceSource(ServerConfig serverConfig) { + + // default for web application, override this for file system + String defaultDir = serverConfig.getResourceDirectory(); + + + // the default... check if a webapp first... + ServletContext sc = GlobalProperties.getServletContext(); + if (sc != null) { + // servlet container so use ServletContext.getResource() + if (defaultDir == null) { + defaultDir = "WEB-INF/ebean"; + } + return new UrlResourceSource(sc, defaultDir); + + } + // use File System directory + return createFileSource(defaultDir); + } + + private static ResourceSource createFileSource(String fileDir) { + + if (fileDir != null) { + // explicitly stated so + File dir = new File(fileDir); + if (dir.exists()) { + logger.info("ResourceManager initialised: type[file] [" + fileDir + "]"); + return new FileResourceSource(fileDir); + } else { + String msg = "ResourceManager could not find directory [" + fileDir + "]"; + throw new NotFoundException(msg); + } + } + + // try to guess the directory starting from the current working + // directory, and searching to a maximum depth of 3 subdirectories + File guessDir = DirectoryFinder.find(null, "WEB-INF", 3); + if (guessDir != null) { + // Typically this means we found the WEB-INF directory below the + // current working directory + logger.info("ResourceManager initialised: type[file] [" + guessDir.getPath() + "]"); + return new FileResourceSource(guessDir.getPath()); + } + + // default to the current working directory + File workingDir = new File("."); + return new FileResourceSource(workingDir); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/resource/package.html b/src/main/java/com/avaje/ebeaninternal/server/resource/package.html new file mode 100644 index 000000000..abed4e076 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/resource/package.html @@ -0,0 +1,10 @@ + + + + Resource Management + + +Resource Management + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/GenSuffix.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/GenSuffix.java new file mode 100644 index 000000000..a4902e84b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/GenSuffix.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.subclass; + +/** + * The suffix used build a generated EntityBean class. + *

    + * Note that the server name can be appended after + *

    + */ +public interface GenSuffix { + + /** + * The suffix added to the super class name. + */ + public static final String SUFFIX = "$$EntityBean"; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/GetterSetterMethods.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/GetterSetterMethods.java new file mode 100644 index 000000000..13c392e84 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/GetterSetterMethods.java @@ -0,0 +1,37 @@ +package com.avaje.ebeaninternal.server.subclass; + +import java.util.List; + +import com.avaje.ebean.enhance.agent.ClassMeta; +import com.avaje.ebean.enhance.agent.EnhanceConstants; +import com.avaje.ebean.enhance.agent.FieldMeta; +import com.avaje.ebean.enhance.asm.ClassVisitor; +import com.avaje.ebean.enhance.asm.Opcodes; + +public class GetterSetterMethods implements Opcodes, EnhanceConstants { + + /** + * Add getters and setters to for interception. + *

    + * Note that we don't intercept Id properties and we don't intercept setters + * on 'OneToMany' properties etc. + *

    + */ + public static void add(ClassVisitor cv, ClassMeta classMeta) { + + List localFields = classMeta.getLocalFields(); + for (int x = 0; x < localFields.size(); x++) { + FieldMeta fieldMeta = localFields.get(x); + fieldMeta.addPublicGetSetMethods(cv, classMeta, true); + } + + List inheritedFields = classMeta.getInheritedFields(); + for (int i = 0; i < inheritedFields.size(); i++) { + FieldMeta fieldMeta = inheritedFields.get(i); + // for persistent inherited fields add a + // getter and setter to enable interception + fieldMeta.addPublicGetSetMethods(cv, classMeta, false); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/MethodWriteReplace.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/MethodWriteReplace.java new file mode 100644 index 000000000..764d33ec5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/MethodWriteReplace.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.subclass; + +import com.avaje.ebean.enhance.agent.ClassMeta; +import com.avaje.ebean.enhance.agent.EnhanceConstants; +import com.avaje.ebean.enhance.asm.ClassVisitor; +import com.avaje.ebean.enhance.asm.Label; +import com.avaje.ebean.enhance.asm.MethodVisitor; +import com.avaje.ebean.enhance.asm.Opcodes; + +/** + * Add a writeReplace method to support optional serialization to vanilla beans. + * + *
    
    + * private Object writeReplace() throws ObjectStreamException {
    + * 	return ebeanIntercept.writeReplaceIntercept();
    + * }
    + * 
    + */ +public class MethodWriteReplace implements Opcodes, EnhanceConstants { + + /** + * Add a writeReplace() method. + */ + public static void add(ClassVisitor cv, ClassMeta classMeta) { + + MethodVisitor mv = cv.visitMethod(ACC_PRIVATE, "writeReplace", "()Ljava/lang/Object;", + null, new String[] { "java/io/ObjectStreamException" }); + + mv.visitCode(); + Label l0 = new Label(); + mv.visitLabel(l0); + mv.visitLineNumber(1, l0); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, classMeta.getClassName(), INTERCEPT_FIELD, L_INTERCEPT); + mv.visitMethodInsn(INVOKEVIRTUAL, C_INTERCEPT, "writeReplaceIntercept","()Ljava/lang/Object;"); + + mv.visitInsn(ARETURN); + Label l1 = new Label(); + mv.visitLabel(l1); + mv.visitLocalVariable("this", "L"+classMeta.getClassName()+";", null, l0, l1, 0); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassClassAdpater.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassClassAdpater.java new file mode 100644 index 000000000..576941c1f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassClassAdpater.java @@ -0,0 +1,265 @@ +package com.avaje.ebeaninternal.server.subclass; + +import java.util.logging.Logger; + +import com.avaje.ebean.enhance.agent.AlreadyEnhancedException; +import com.avaje.ebean.enhance.agent.ClassMeta; +import com.avaje.ebean.enhance.agent.EnhanceConstants; +import com.avaje.ebean.enhance.agent.EnhanceContext; +import com.avaje.ebean.enhance.agent.IndexFieldWeaver; +import com.avaje.ebean.enhance.agent.InterceptField; +import com.avaje.ebean.enhance.agent.MarkerField; +import com.avaje.ebean.enhance.agent.MethodEquals; +import com.avaje.ebean.enhance.agent.MethodIsEmbeddedNewOrDirty; +import com.avaje.ebean.enhance.agent.MethodNewInstance; +import com.avaje.ebean.enhance.agent.MethodPropertyChangeListener; +import com.avaje.ebean.enhance.agent.MethodSetEmbeddedLoaded; +import com.avaje.ebean.enhance.agent.NoEnhancementRequiredException; +import com.avaje.ebean.enhance.agent.VisitMethodParams; +import com.avaje.ebean.enhance.asm.AnnotationVisitor; +import com.avaje.ebean.enhance.asm.ClassAdapter; +import com.avaje.ebean.enhance.asm.ClassVisitor; +import com.avaje.ebean.enhance.asm.FieldVisitor; +import com.avaje.ebean.enhance.asm.MethodVisitor; +import com.avaje.ebean.enhance.asm.Opcodes; + +public class SubClassClassAdpater extends ClassAdapter implements EnhanceConstants { + + static final Logger logger = Logger.getLogger(SubClassClassAdpater.class.getName()); + + final EnhanceContext enhanceContext; + + final ClassLoader classLoader; + + final ClassMeta classMeta; + + final String subClassSuffix; + + boolean firstMethod = true; + + public SubClassClassAdpater(String subClassSuffix, ClassVisitor cv, ClassLoader classLoader, EnhanceContext context) { + super(cv); + this.subClassSuffix = subClassSuffix; + this.classLoader = classLoader; + this.enhanceContext = context; + this.classMeta = context.createClassMeta(); + } + + public boolean isLog(int level){ + return classMeta.isLog(level); + } + + public void log(String msg){ + classMeta.log(msg); + } + + /** + * Create the class definition replacing the className and super class. + */ + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + + // Note: interfaces can be an empty array but not null + int n = 1 + interfaces.length; + String[] c = new String[n]; + for (int i = 0; i < interfaces.length; i++) { + c[i] = interfaces[i]; + if (c[i].equals(C_ENTITYBEAN)) { + throw new AlreadyEnhancedException(name); + } + if (c[i].equals(C_SCALAOBJECT)) { + classMeta.setScalaInterface(true); + } + if (c[i].equals(C_GROOVYOBJECT)) { + classMeta.setGroovyInterface(true); + } + } + + // Add the EntityBean interface + c[c.length - 1] = C_ENTITYBEAN; + + if (!superName.equals("java/lang/Object")){ + + ClassMeta superMeta = enhanceContext.getSuperMeta(superName, classLoader); + if (superMeta != null) { + classMeta.setSuperMeta(superMeta); + if (classMeta.isLog(2)){ + classMeta.log("entity inheritance "+superMeta.getDescription()); + } + } + } + + // adjust the superName and name as we + // are actually creating a subclass of + // the class being visited + superName = name; + name = name+subClassSuffix; + + classMeta.setClassName(name, superName); + + super.visit(version, access, name, signature, superName, c); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + classMeta.addClassAnnotation(desc); + return super.visitAnnotation(desc, visible); + } + + /** + * The ebeanIntercept field is added once but thats all. Note the other + * fields are defined in the superclass. + */ + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + + if ((access & Opcodes.ACC_STATIC) != 0) { + // no interception of static fields + if (isLog(2)){ + log("Skip intercepting static field "+name); + } + return null; + } + + if ((access & Opcodes.ACC_TRANSIENT) != 0) { + // no interception of transient fields + if (classMeta.isLog(2)){ + classMeta.log("Skip intercepting transient field "+name); + } + return null; + } + + // read the field and associated annotations... + if (classMeta.isLog(5)){ + classMeta.log(" ... reading field:"+name+" desc:"+desc); + } + + return classMeta.createLocalFieldVisitor(name, desc); + } + + /** + * Replace the method code with calls to super. Add the intercept code as + * required. + */ + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + + if (firstMethod){ + if (!classMeta.isEntityEnhancementRequired()) { + // skip the rest of the visiting etc + throw new NoEnhancementRequiredException(); + } + + // always add the marker field on every enhanced class + String marker = MarkerField.addField(cv, classMeta.getClassName()); + if (isLog(4)){ + log("... add marker field \""+marker+"\""); + log("... add intercept and identity fields"); + } + + // always add these fields for subclass generation + InterceptField.addField(cv, enhanceContext.isTransientInternalFields()); + MethodEquals.addIdentityField(cv); + firstMethod = false; + } + + + VisitMethodParams params = new VisitMethodParams(cv, access, name, desc, signature, exceptions); + + if (isDefaultConstructor(access, name, desc, signature, exceptions)){ + SubClassConstructor.add(params, classMeta); + return null; + } + + if (isSpecialMethod(access, name, desc)) { + return null; + } + + // register the method so that we can check + // if it exists when GetterSetterMethods.add() + // is called. May not exist on read only type + // entity beans such as the internal meta beans. + classMeta.addExistingSuperMethod(name, desc); + + return null; + } + + /** + * Add methods to get and set the entityBeanIntercept. Also add the + * writeReplace method to control serialisation. + */ + public void visitEnd() { + + if (!classMeta.isEntityEnhancementRequired()){ + throw new NoEnhancementRequiredException(); + } + + if (!classMeta.hasDefaultConstructor()){ + if (isLog(2)){ + log("... adding default constructor"); + } + SubClassConstructor.addDefault(cv, classMeta); + } + + MarkerField.addGetMarker(cv, classMeta.getClassName()); + + // Add the _ebean_getIntercept() _ebean_setIntercept() methods + InterceptField.addGetterSetter(cv, classMeta.getClassName()); + + // Add add/removePropertyChangeListener methods + MethodPropertyChangeListener.addMethod(cv, classMeta); + + // Add getter and setter methods for both local + // and inherited properties + GetterSetterMethods.add(cv, classMeta); + + // Add extra methods such as getField(index) etc + IndexFieldWeaver.addMethods(cv, classMeta); + + MethodSetEmbeddedLoaded.addMethod(cv, classMeta); + MethodIsEmbeddedNewOrDirty.addMethod(cv, classMeta); + MethodNewInstance.addMethod(cv, classMeta); + + // add a writeReplace method to control serialisation + MethodWriteReplace.add(cv, classMeta); + + // register with the context + enhanceContext.addClassMeta(classMeta); + + super.visitEnd(); + } + + /** + * Return true if this is the default (no arg) constructor. + */ + private boolean isDefaultConstructor(int access, String name, String desc, String signature, + String[] exceptions){ + + if (name.equals("") && desc.equals("()V")) { + classMeta.setHasDefaultConstructor(true); + return true; + } + + return false; + } + + /** + * Take note of hashcode and equals. + */ + private boolean isSpecialMethod(int access, String name, String desc) { + + if (name.equals("hashCode") && desc.equals("()I")) { + classMeta.setHasEqualsOrHashcode(true); + return true; + } + + if (name.equals("equals") && desc.equals("(Ljava/lang/Object;)Z")) { + classMeta.setHasEqualsOrHashcode(true); + return true; + } + + return false; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassConstructor.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassConstructor.java new file mode 100644 index 000000000..4ec0dee13 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassConstructor.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.subclass; + +import com.avaje.ebean.enhance.agent.ClassMeta; +import com.avaje.ebean.enhance.agent.EnhanceConstants; +import com.avaje.ebean.enhance.agent.VisitMethodParams; +import com.avaje.ebean.enhance.asm.ClassVisitor; +import com.avaje.ebean.enhance.asm.Label; +import com.avaje.ebean.enhance.asm.MethodVisitor; +import com.avaje.ebean.enhance.asm.Opcodes; + +public class SubClassConstructor implements Opcodes, EnhanceConstants{ + + public static void addDefault(ClassVisitor cv, ClassMeta meta) { + VisitMethodParams params = new VisitMethodParams(cv, Opcodes.ACC_PUBLIC, "", "()V", null, null); + add(params, meta); + } + + public static void add(VisitMethodParams params, ClassMeta meta) { + + String className = meta.getClassName(); + String superClassName = meta.getSuperClassName(); + + if (params.forcePublic()){ + if (meta.isLog(0)){ + meta.log(" forcing ACC_PUBLIC "); + } + } + + MethodVisitor mv = params.visitMethod(); + //mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + Label l0 = new Label(); + mv.visitLabel(l0); + mv.visitLineNumber(17, l0); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, superClassName, "", "()V"); + Label l1 = new Label(); + mv.visitLabel(l1); + mv.visitLineNumber(18, l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitTypeInsn(NEW, C_INTERCEPT); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, C_INTERCEPT, "", "(Ljava/lang/Object;)V"); + mv.visitFieldInsn(PUTFIELD, className, INTERCEPT_FIELD, L_INTERCEPT); + Label l2 = new Label(); + mv.visitLabel(l2); + mv.visitLineNumber(19, l2); + mv.visitInsn(RETURN); + Label l3 = new Label(); + mv.visitLabel(l3); + mv.visitLocalVariable("this", "L"+className+";", null, l0, l3, 0); + mv.visitMaxs(4, 1); + mv.visitEnd(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassFactory.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassFactory.java new file mode 100644 index 000000000..56527240c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassFactory.java @@ -0,0 +1,127 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.subclass; + +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.enhance.agent.ClassPathClassBytesReader; +import com.avaje.ebean.enhance.agent.EnhanceConstants; +import com.avaje.ebean.enhance.agent.EnhanceContext; +import com.avaje.ebean.enhance.asm.ClassReader; +import com.avaje.ebean.enhance.asm.ClassWriter; + +/** + * Creates Classes that implement EntityBean for a given normal bean Class. + *

    + * This dynamically creates a subclass of a normal bean class. The subclass has + * method interception to handle the lazy loading of references and old values + * creation. + *

    + */ +public class SubClassFactory extends ClassLoader implements EnhanceConstants, GenSuffix { + + private static final Logger logger = Logger.getLogger(SubClassFactory.class.getName()); + + private static final int CLASS_WRITER_FLAGS = ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS; + + private final EnhanceContext enhanceContext; + + private final ClassLoader parentClassLoader; + + + /** + * Create with a given ClassLoader. + */ + public SubClassFactory(ClassLoader parent, int logLevel) { + super(parent); + parentClassLoader = parent; + + ClassPathClassBytesReader reader = new ClassPathClassBytesReader(null); + enhanceContext = new EnhanceContext(reader, true, "debug="+logLevel); + } + + /** + * Create a subclass for the given bean class that implements EntityBean interface. + *

    + * The transientGetters is a list of getter methods that are considered + * no persistent. That is, when they are called the bean should NOT + * trigger creation of an 'old values' copy of the beans values. + *

    + */ + public Class create(Class normalClass, String serverName) throws IOException { + + String subClassSuffix = EnhanceConstants.SUFFIX; + if (serverName != null){ + subClassSuffix += "$"+serverName; + } + + // Note: these have periods rather than slashes + String clsName = normalClass.getName(); + String subClsName = clsName+subClassSuffix; + + try { + byte[] newClsBytes = subclassBytes(clsName, subClassSuffix); + + Class newCls = defineClass(subClsName, newClsBytes, 0, newClsBytes.length); + return newCls; + + } catch (IOException ex){ + String m = "Error creating subclass for ["+clsName+"]"; + logger.log(Level.SEVERE, m, ex); + throw ex; + + } catch (Throwable ex){ + String m = "Error creating subclass for ["+clsName+"]"; + logger.log(Level.SEVERE, m, ex); + throw new RuntimeException(ex); + } + } + + + /** + * Return byte code for the subclass. + *

    + * Note that if transientInfo is null, then no interception of getters or setters + * takes place. + *

    + */ + private byte[] subclassBytes(String className, String subClassSuffix) + throws IOException { + + String resName = className.replace('.', '/')+".class"; + + InputStream is = getResourceAsStream(resName); + + ClassReader cr = new ClassReader(is); + ClassWriter cw = new ClassWriter(CLASS_WRITER_FLAGS); + + SubClassClassAdpater ca = new SubClassClassAdpater(subClassSuffix, cw, parentClassLoader, enhanceContext); + if (ca.isLog(1)) { + ca.log(" enhancing " + className+subClassSuffix); + } + + cr.accept(ca, 0); + + return cw.toByteArray(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassGenerator.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassGenerator.java new file mode 100644 index 000000000..56a9249ed --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassGenerator.java @@ -0,0 +1,272 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.subclass; + + +/** + * Used to generate a subclass based on a bean. + *

    + * It does not have the fields or private methods of the read class. It replaces + * the method code with calls to super instead. It may need to add hashCode() + * and equals() methods to make sure a reference is loaded prior to either of + * these methods being called. It uses writeReplace() to modify the + * serialisation. + *

    + */ +public class SubClassGenerator {// extends ClassAdapter implements Opcodes, GenConstants { + +// private static final Logger logger = LogFactory.get(SubClassGenerator.class); +// +// boolean isInterceptFieldAdded = false; +// +// boolean isAddClonable = true; +// +// boolean superHasEquals = false; +// +// ClassInfo info; +// +// MethodInfo methodInfo; +// +// boolean hasSuperClass; +// +// /** +// * Create with the ClassInfo. +// */ +// public SubClassGenerator(ClassVisitor cv, ClassInfo info) { +// super(cv); +// this.info = info; +// this.methodInfo = info.getMethodInfo(); +// } +// +// /** +// * Create the class definition replacing the className and super class. +// */ +// public void visit(int version, int access, String name, String signature, String superName, +// String[] interfaces) { +// +// // Note: These have slashes rather than periods!! +// String className = name+info.getSuffix(); +// String superClassName; +// if ("java/lang/Object".endsWith(superName)){ +// superClassName = name; +// } else { +// hasSuperClass = true; +// superClassName = name;//superName+info.getSuffix(); +// } +// +// info.setClassName(className); +// info.setSuperClassName(superClassName); +// +// +// // Note: interfaces can be an empty array but not null +// int n = 1 + interfaces.length; +// String[] c = new String[n]; +// System.arraycopy(interfaces, 0, c, 0, interfaces.length); +// +// // Add the EntityBean interface +// c[c.length - 1] = ENTITYBEAN; +// +// super.visit(version, access, className, signature, superClassName, c); +// } +// +// /** +// * The ebeanIntercept field is added once but thats all. Note the other +// * fields are defined in the superclass. +// */ +// public FieldVisitor visitField(int access, String name, String desc, String signature, +// Object value) { +// +// if (!isInterceptFieldAdded) { +// +// FieldVisitor f0 = cv.visitField(ACC_PRIVATE + ACC_VOLATILE, IDENTITY_FIELD_NAME, "Ljava/lang/Object;", null, null); +// f0.visitEnd(); +// +// FieldVisitor f1 = cv.visitField(0, INTERCEPT_FIELD_NAME, L_INTERCEPT, null, null); +// f1.visitEnd(); +// +// isInterceptFieldAdded = true; +// return null; +// } +// +// return null; +// } +// +// /** +// * Replace the method code with calls to super. Add the intercept code as +// * required. +// */ +// public MethodVisitor visitMethod(int access, String name, String desc, String signature, +// String[] exceptions) { +// +// boolean isPrivate = ((access & Opcodes.ACC_PRIVATE) != 0); +// boolean isStatic = ((access & Opcodes.ACC_STATIC) != 0); +// if (isPrivate || isStatic) { +// // no intercept on static or private methods +// return null; +// } +// // the key to look up in methodInfo +// String methodKey = name + ":" + desc; +// +// if (hasSuperClass){ +// if (logger.isLoggable(Level.FINER)){ +// String msg = "existing methods "+info.getClassName()+" "+methodKey; +// logger.finer(msg); +// } +// } +// +// VisitMethodParams params = new VisitMethodParams(cv, access, name, desc, signature, exceptions); +// +// if (methodInfo.isSet(methodKey)) { +// // for persistent properties excluding assoc Many's & id +// // ie. Old values not created for id or assoc many. +// return new ProxySetterMethod(params, info, methodInfo); +// } +// +// if (methodInfo.isGet(methodKey)) { +// // for persistent properties excluding id properties. +// // ie. reference loading not fired for id properties. +// return new ProxyGetterMethod(params, info); +// } +// +// if ("".equals(name)) { +// return new ProxyConstructor(params, info); +// } +// +// if ("hashCode:()I".equals(methodKey)) { +// return new ProxyMethod(params, info); +// } +// +// if ("clone:()Ljava/lang/Object;".equals(methodKey)) { +// // SuperClass has a clone() method +// isAddClonable = false; +// return new MethodClone(params, info); +// } +// +// if ("toString:()Ljava/lang/String;".equals(methodKey)) { +// // No intercept on toString() as used by debuggers etc +// return null; +// } +// if ("hashCode:()I".equals(methodKey)) { +// return null; +// } +// if ("equals:(Ljava/lang/Object;)Z".equals(methodKey)) { +// superHasEquals = true; +// return null; +// } +// +// return null; +// } +// +// /** +// * Add methods to get and set the entityBeanIntercept. Also add the +// * writeReplace method to control serialisation. +// */ +// public void visitEnd() { +// +// if (isAddClonable){ +// // super has not overwritten the clone() method. +// // we will add the clone() method in case a super of the super has clone() +// String[] exceptions = new String[] { "java/lang/CloneNotSupportedException" }; +// VisitMethodParams params = new VisitMethodParams(cv, ACC_PUBLIC, "clone", "()Ljava/lang/Object;", null, exceptions); +// MethodClone methodClone = new MethodClone(params, info); +// methodClone.visitCode(); +// } +// +// MethodInfo methodInfo = info.getMethodInfo(); +// if (methodInfo.isEmbedded()){ +// // don't override equals etc when it is an embedded bean +// // Either EmbeddedId or a Embeddable +// +// } else if (methodInfo.overrideEquals(superHasEquals)) { +// // we want to generate a equals() hashCode() and ebeanGetIndentity() +// // methods so that the generated subclass has built in equals() support. +// +// if (methodInfo.getIdGetter() == null) { +// if (methodInfo.isSqlSelectBased()){ +// // This could be common for reporting type beans based on +// // sql-select that use group by type queries. +// } else { +// String m = "Can not generate equals for ["+info.getClassName(); +// m += "]. Concatinated id?"; +// logger.warning(m); +// } +// } else { +// +// if (generateEbeanGetIdentityMethod()){ +// // add equals() +// MethodEquals.add(cv, info); +// +// // add hashCode() +// MethodHashCode.add(cv, info); +// } +// } +// } +// +// // add additional getters from super class inheritance +// List additionalGetters = methodInfo.getAdditionalGetters(); +// for (MethodDesc methodDesc : additionalGetters) { +// VisitMethodParams params = new VisitMethodParams(cv, ACC_PUBLIC, methodDesc); +// ProxyGetterMethod getter = new ProxyGetterMethod(params, info); +// getter.visitCode(); +// } +// +// // add additional setters from super class inheritance +// List additionalSetters = methodInfo.getAdditionalSetters(); +// for (MethodDesc methodDesc : additionalSetters) { +// VisitMethodParams params = new VisitMethodParams(cv, ACC_PUBLIC, methodDesc); +// ProxySetterMethod setter = new ProxySetterMethod(params, info, methodInfo); +// setter.visitCode(); +// } +// +// // add set get methods for ebeanIntecept +// MethodGetSetIntercept.add(cv, info); +// +// // add a writeReplace method to control serialisation +// MethodWriteReplace.add(cv, info); +// +// super.visitEnd(); +// } +// +// private boolean generateEbeanGetIdentityMethod() { +// String idGetterDesc = methodInfo.getIdGetterDesc(); +// if (idGetterDesc.equals("()I")) { +// // int version of ebeanGetIndentity() +// MethodEbeanGetIdentityInt.add(cv, info); +// return true; +// +// } else if (idGetterDesc.equals("()J")) { +// // long version of ebeanGetIndentity() +// MethodEbeanGetIdentityLong.add(cv, info); +// return true; +// +// } else if (idGetterDesc.length() > 5) { +// // Object version of ebeanGetIndentity() +// MethodEbeanGetIdentity.add(cv, info); +// return true; +// +// } else { +// String m = "Can not generate equals for ["+info.getClassName(); +// m += "] due to type of id property: "+idGetterDesc; +// logger.warning(m); +// return false; +// } +// } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassManager.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassManager.java new file mode 100644 index 000000000..35794357a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassManager.java @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.subclass; + +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.enhance.agent.EnhanceConstants; +import com.avaje.ebeaninternal.api.ClassUtil; + +/** + * Creates and caches the dynamically generated subclasses. + *

    + * That is, the 'EntityBean' classes are dynamically generated subclasses of the + * 'vanilla' classes. + *

    + */ +public class SubClassManager implements EnhanceConstants { + + private static final Logger logger = Logger.getLogger(SubClassManager.class.getName()); + + private final ConcurrentHashMap> clzMap; + + private final SubClassFactory subclassFactory; + + private final String serverName; + + /** + * The log level for debugging subclass generation/enhancement. + */ + private final int logLevel; + + /** + * Construct with the ClassLoader used to load Ebean.class. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public SubClassManager(ServerConfig serverConfig) { + + String s = serverConfig.getProperty("subClassManager.preferContextClassloader", "true"); + final boolean preferContext = "true".equalsIgnoreCase(s); + + this.serverName = serverConfig.getName(); + this.logLevel = serverConfig.getEnhanceLogLevel(); + this.clzMap = new ConcurrentHashMap>(); + + try { + subclassFactory = (SubClassFactory) AccessController + .doPrivileged(new PrivilegedExceptionAction() { + public Object run() { + ClassLoader cl = ClassUtil.getClassLoader(this.getClass(), preferContext); + logger.info("SubClassFactory parent ClassLoader ["+cl.getClass().getName()+"]"); + return new SubClassFactory(cl, logLevel); + } + }); + } catch (PrivilegedActionException e) { + throw new PersistenceException(e); + } + } + + /** + * Resolve the Class for the class name. + *

    + * The methodInfo is used to determine the method interception on the + * generated class. + *

    + *

    + * If the class has already been generated then it is returned out of a + * cache. + *

    + */ + public Class resolve(String name) { + + synchronized (this) { + String superName = SubClassUtil.getSuperClassName(name); + Class clz = clzMap.get(superName); + if (clz == null) { + clz = createClass(superName); + clzMap.put(superName, clz); + } + return clz; + } + } + + private Class createClass(String name) { + + try { + + Class superClass = Class.forName(name, true, subclassFactory.getParent()); + + return subclassFactory.create(superClass, serverName); + + } catch (Exception ex) { + String m = "Error creating subclass for [" + name + "]"; + throw new PersistenceException(m, ex); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassUtil.java b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassUtil.java new file mode 100644 index 000000000..7112a6926 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/SubClassUtil.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.subclass; + + +/** + * Helper methods for generated sub classes. + */ +public class SubClassUtil implements GenSuffix { + + /** + * Return true if this is a generated class. + */ + public static boolean isSubClass(String className) { + + return (className.lastIndexOf(SUFFIX) != -1); + } + + /** + * Return the super class name given the generated className. + */ + public static String getSuperClassName(String className){ + int dPos = className.lastIndexOf(SUFFIX); + if (dPos > -1){ + return className.substring(0, dPos); + } + return className; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/subclass/package.html b/src/main/java/com/avaje/ebeaninternal/server/subclass/package.html new file mode 100644 index 000000000..e16d9d55d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/subclass/package.html @@ -0,0 +1,15 @@ + + + + Used to generate subclasses rather than weaving + + +Used to generate subclasses rather than weaving +

    +As alternative to weaving/enhancing the classes via javaagent or ant you +can use dynamically generated subclasses. These objects support that feature. +

    + + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/csv/CsvUtilReader.java b/src/main/java/com/avaje/ebeaninternal/server/text/csv/CsvUtilReader.java new file mode 100644 index 000000000..10baf82b8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/csv/CsvUtilReader.java @@ -0,0 +1,249 @@ +package com.avaje.ebeaninternal.server.text.csv; + +// Original name: au.com.bytecode.opencsv.CSVReader +// rbygrave: Made some Java Generics tweaks to remove warnings + +/** + Copyright 2005 Bytecode Pty Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +/** + * Glen Smith's CSV reader released under Apache License version 2. + * + * @author Glen Smith + * + */ +public class CsvUtilReader { + + private BufferedReader br; + + private boolean hasNext = true; + + private char separator; + + private char quotechar; + + private int skipLines; + + private boolean linesSkiped; + + /** The default separator to use if none is supplied to the constructor. */ + public static final char DEFAULT_SEPARATOR = ','; + + /** + * The default quote character to use if none is supplied to the + * constructor. + */ + public static final char DEFAULT_QUOTE_CHARACTER = '"'; + + /** + * The default line to start reading. + */ + public static final int DEFAULT_SKIP_LINES = 0; + + /** + * Constructs CSVReader using a comma for the separator. + * + * @param reader + * the reader to an underlying CSV source. + */ + public CsvUtilReader(Reader reader) { + this(reader, DEFAULT_SEPARATOR); + } + + /** + * Constructs CSVReader with supplied separator. + * + * @param reader + * the reader to an underlying CSV source. + * @param separator + * the delimiter to use for separating entries. + */ + public CsvUtilReader(Reader reader, char separator) { + this(reader, separator, DEFAULT_QUOTE_CHARACTER); + } + + + + /** + * Constructs CSVReader with supplied separator and quote char. + * + * @param reader + * the reader to an underlying CSV source. + * @param separator + * the delimiter to use for separating entries + * @param quotechar + * the character to use for quoted elements + */ + public CsvUtilReader(Reader reader, char separator, char quotechar) { + this(reader, separator, quotechar, DEFAULT_SKIP_LINES); + } + + /** + * Constructs CSVReader with supplied separator and quote char. + * + * @param reader + * the reader to an underlying CSV source. + * @param separator + * the delimiter to use for separating entries + * @param quotechar + * the character to use for quoted elements + * @param line + * the line number to skip for start reading + */ + public CsvUtilReader(Reader reader, char separator, char quotechar, int line) { + this.br = new BufferedReader(reader); + this.separator = separator; + this.quotechar = quotechar; + this.skipLines = line; + } + + /** + * Reads the entire file into a List with each element being a String[] of + * tokens. + * + * @return a List of String[], with each String[] representing a line of the + * file. + * + * @throws IOException + * if bad things happen during the read + */ + public List readAll() throws IOException { + + List allElements = new ArrayList(); + while (hasNext) { + String[] nextLineAsTokens = readNext(); + if (nextLineAsTokens != null) + allElements.add(nextLineAsTokens); + } + return allElements; + + } + + /** + * Reads the next line from the buffer and converts to a string array. + * + * @return a string array with each comma-separated element as a separate + * entry. + * + * @throws IOException + * if bad things happen during the read + */ + public String[] readNext() throws IOException { + + String nextLine = getNextLine(); + return hasNext ? parseLine(nextLine) : null; + } + + /** + * Reads the next line from the file. + * + * @return the next line from the file without trailing newline + * @throws IOException + * if bad things happen during the read + */ + private String getNextLine() throws IOException { + if (!this.linesSkiped) { + for (int i = 0; i < skipLines; i++) { + br.readLine(); + } + this.linesSkiped = true; + } + String nextLine = br.readLine(); + if (nextLine == null) { + hasNext = false; + } + return hasNext ? nextLine : null; + } + + /** + * Parses an incoming String and returns an array of elements. + * + * @param nextLine + * the string to parse + * @return the comma-tokenized list of elements, or null if nextLine is null + * @throws IOException if bad things happen during the read + */ + private String[] parseLine(String nextLine) throws IOException { + + if (nextLine == null) { + return null; + } + + List tokensOnThisLine = new ArrayList(); + + StringBuilder sb = new StringBuilder(); + boolean inQuotes = false; + do { + if (inQuotes) { + // continuing a quoted section, reappend newline + sb.append("\n"); + nextLine = getNextLine(); + if (nextLine == null) + break; + } + for (int i = 0; i < nextLine.length(); i++) { + + char c = nextLine.charAt(i); + if (c == quotechar) { + // this gets complex... the quote may end a quoted block, or escape another quote. + // do a 1-char lookahead: + if( inQuotes // we are in quotes, therefore there can be escaped quotes in here. + && nextLine.length() > (i+1) // there is indeed another character to check. + && nextLine.charAt(i+1) == quotechar ){ // ..and that char. is a quote also. + // we have two quote chars in a row == one quote char, so consume them both and + // put one on the token. we do *not* exit the quoted text. + sb.append(nextLine.charAt(i+1)); + i++; + }else{ + inQuotes = !inQuotes; + // the tricky case of an embedded quote in the middle: a,bc"d"ef,g + if(i>2 //not on the begining of the line + && nextLine.charAt(i-1) != this.separator //not at the begining of an escape sequence + && nextLine.length()>(i+1) && + nextLine.charAt(i+1) != this.separator //not at the end of an escape sequence + ){ + sb.append(c); + } + } + } else if (c == separator && !inQuotes) { + tokensOnThisLine.add(sb.toString().trim()); + sb = new StringBuilder(); // start work on next token + } else { + sb.append(c); + } + } + } while (inQuotes); + tokensOnThisLine.add(sb.toString().trim()); + return tokensOnThisLine.toArray(new String[tokensOnThisLine.size()]); + } + + /** + * Closes the underlying reader. + * + * @throws IOException if the close fails + */ + public void close() throws IOException{ + br.close(); + } + +} + diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/csv/TCsvReader.java b/src/main/java/com/avaje/ebeaninternal/server/text/csv/TCsvReader.java new file mode 100644 index 000000000..fb04ba9b3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/csv/TCsvReader.java @@ -0,0 +1,388 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.csv; + +import java.io.Reader; +import java.sql.Types; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.text.StringParser; +import com.avaje.ebean.text.TextException; +import com.avaje.ebean.text.TimeStringParser; +import com.avaje.ebean.text.csv.CsvCallback; +import com.avaje.ebean.text.csv.CsvReader; +import com.avaje.ebean.text.csv.DefaultCsvCallback; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * + * @author rbygrave + */ +public class TCsvReader implements CsvReader { + + // private static final Logger logger = + // Logger.getLogger(TCsvReader.class.getName()); + + private static final TimeStringParser TIME_PARSER = new TimeStringParser(); + + private final EbeanServer server; + + private final BeanDescriptor descriptor; + + private final List columnList = new ArrayList(); + + private final CsvColumn ignoreColumn = new CsvColumn(); + + private boolean treatEmptyStringAsNull = true; + + private boolean hasHeader; + + private int logInfoFrequency = 1000; + + private String defaultTimeFormat = "HH:mm:ss"; + private String defaultDateFormat = "yyyy-MM-dd"; + private String defaultTimestampFormat = "yyyy-MM-dd hh:mm:ss.fffffffff"; + private Locale defaultLocale = Locale.getDefault(); + + /** + * The batch size used for JDBC statement batching. + */ + protected int persistBatchSize = 30; + + private boolean addPropertiesFromHeader; + + // private String addHeaderDateTimeFormat; + // private Locale addHeaderLocale; + + public TCsvReader(EbeanServer server, BeanDescriptor descriptor) { + this.server = server; + this.descriptor = descriptor; + } + + public void setDefaultLocale(Locale defaultLocale) { + this.defaultLocale = defaultLocale; + } + + public void setDefaultTimeFormat(String defaultTimeFormat) { + this.defaultTimeFormat = defaultTimeFormat; + } + + public void setDefaultDateFormat(String defaultDateFormat) { + this.defaultDateFormat = defaultDateFormat; + } + + public void setDefaultTimestampFormat(String defaultTimestampFormat) { + this.defaultTimestampFormat = defaultTimestampFormat; + } + + public void setPersistBatchSize(int persistBatchSize) { + this.persistBatchSize = persistBatchSize; + } + + public void setIgnoreHeader() { + setHasHeader(true, false); + } + + public void setAddPropertiesFromHeader() { + setHasHeader(true, true); + } + + public void setHasHeader(boolean hasHeader, boolean addPropertiesFromHeader) { + this.hasHeader = hasHeader; + this.addPropertiesFromHeader = addPropertiesFromHeader; + } + + public void setLogInfoFrequency(int logInfoFrequency) { + this.logInfoFrequency = logInfoFrequency; + } + + public void addIgnore() { + columnList.add(ignoreColumn); + } + + public void addProperty(String propertyName) { + addProperty(propertyName, null); + } + + public void addReference(String propertyName) { + addProperty(propertyName, null, true); + } + + public void addProperty(String propertyName, StringParser parser) { + addProperty(propertyName, parser, false); + } + + public void addDateTime(String propertyName, String dateTimeFormat) { + addDateTime(propertyName, dateTimeFormat, Locale.getDefault()); + } + + public void addDateTime(String propertyName, String dateTimeFormat, Locale locale) { + + ElPropertyValue elProp = descriptor.getElGetValue(propertyName); + if (!elProp.isDateTimeCapable()) { + throw new TextException("Property " + propertyName + " is not DateTime capable"); + } + + if (dateTimeFormat == null) { + dateTimeFormat = getDefaultDateTimeFormat(elProp.getJdbcType()); + } + + if (locale == null) { + locale = defaultLocale; + } + + SimpleDateFormat sdf = new SimpleDateFormat(dateTimeFormat, locale); + DateTimeParser parser = new DateTimeParser(sdf, dateTimeFormat, elProp); + + CsvColumn column = new CsvColumn(elProp, parser, false); + columnList.add(column); + } + + private String getDefaultDateTimeFormat(int jdbcType) { + switch (jdbcType) { + case Types.TIME: + return defaultTimeFormat; + case Types.DATE: + return defaultDateFormat; + case Types.TIMESTAMP: + return defaultTimestampFormat; + + default: + throw new RuntimeException("Expected java.sql.Types TIME,DATE or TIMESTAMP but got [" + jdbcType + "]"); + } + } + + public void addProperty(String propertyName, StringParser parser, boolean reference) { + + ElPropertyValue elProp = descriptor.getElGetValue(propertyName); + if (parser == null) { + parser = elProp.getStringParser(); + } + CsvColumn column = new CsvColumn(elProp, parser, reference); + columnList.add(column); + } + + public void process(Reader reader) throws Exception { + DefaultCsvCallback callback = new DefaultCsvCallback(persistBatchSize, logInfoFrequency); + process(reader, callback); + } + + public void process(Reader reader, CsvCallback callback) throws Exception { + + if (reader == null) { + throw new NullPointerException("reader is null?"); + } + if (callback == null) { + throw new NullPointerException("callback is null?"); + } + + CsvUtilReader utilReader = new CsvUtilReader(reader); + + callback.begin(server); + + int row = 0; + + if (hasHeader) { + String[] line = utilReader.readNext(); + if (addPropertiesFromHeader) { + addPropertiesFromHeader(line); + } + callback.readHeader(line); + } + + try { + do { + ++row; + String[] line = utilReader.readNext(); + if (line == null) { + --row; + break; + } + + if (callback.processLine(row, line)) { + // the line content is expected to be ok for processing + if (line.length != columnList.size()) { + // we have not got the expected number of columns + String msg = "Error at line " + row + ". Expected [" + columnList.size() + "] columns " + + "but instead we have [" + line.length + "]. Line[" + Arrays.toString(line) + "]"; + throw new TextException(msg); + } + + T bean = buildBeanFromLineContent(row, line); + + callback.processBean(row, line, bean); + + } + } while (true); + + callback.end(row); + + } catch (Exception e) { + // notify that an error occured so that any + // transaction can be rolled back if required + callback.endWithError(row, e); + throw e; + } + } + + private void addPropertiesFromHeader(String[] line) { + for (int i = 0; i < line.length; i++) { + ElPropertyValue elProp = descriptor.getElGetValue(line[i]); + if (elProp == null) { + throw new TextException("Property [" + line[i] + "] not found"); + } + + if (Types.TIME == elProp.getJdbcType()) { + addProperty(line[i], TIME_PARSER); + + } else if (isDateTimeType(elProp.getJdbcType())) { + addDateTime(line[i], null, null); + + } else if (elProp.isAssocProperty()) { + BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne) elProp.getBeanProperty(); + String idProp = assocOne.getBeanDescriptor().getIdBinder().getIdProperty(); + addReference(line[i] + "." + idProp); + } else { + addProperty(line[i]); + } + } + } + + private boolean isDateTimeType(int t) { + if (t == Types.TIMESTAMP || t == Types.DATE || t == Types.TIME) { + return true; + } + return false; + } + + @SuppressWarnings("unchecked") + protected T buildBeanFromLineContent(int row, String[] line) { + + try { + EntityBean entityBean = descriptor.createEntityBean(); + T bean = (T) entityBean; + + int columnPos = 0; + for (; columnPos < line.length; columnPos++) { + convertAndSetColumn(columnPos, line[columnPos], entityBean); + } + + return bean; + + } catch (RuntimeException e) { + String msg = "Error at line: " + row + " line[" + Arrays.toString(line) + "]"; + throw new RuntimeException(msg, e); + } + } + + protected void convertAndSetColumn(int columnPos, String strValue, Object bean) { + + strValue = strValue.trim(); + + if (strValue.length() == 0 && treatEmptyStringAsNull) { + return; + } + + CsvColumn c = columnList.get(columnPos); + c.convertAndSet(strValue, bean); + } + + /** + * Processes a column in the csv content. + */ + public static class CsvColumn { + + private final ElPropertyValue elProp; + private final StringParser parser; + private final boolean ignore; + private final boolean reference; + + /** + * Constructor for the IGNORE column. + */ + private CsvColumn() { + this.elProp = null; + this.parser = null; + this.reference = false; + this.ignore = true; + } + + /** + * Construct with a property and parser. + */ + public CsvColumn(ElPropertyValue elProp, StringParser parser, boolean reference) { + this.elProp = elProp; + this.parser = parser; + this.reference = reference; + this.ignore = false; + } + + /** + * Convert the string to the appropriate value and set it to the bean. + */ + public void convertAndSet(String strValue, Object bean) { + + if (!ignore) { + Object value = parser.parse(strValue); + elProp.elSetValue(bean, value, true, reference); + } + } + } + + /** + * A StringParser for converting custom date/time/datetime strings into + * appropriate java types (Date, Calendar, SQL Date, Time, Timestamp, JODA + * etc). + */ + private static class DateTimeParser implements StringParser { + + private final DateFormat dateFormat; + private final ElPropertyValue elProp; + private final String format; + + DateTimeParser(DateFormat dateFormat, String format, ElPropertyValue elProp) { + this.dateFormat = dateFormat; + this.elProp = elProp; + this.format = format; + } + + public Object parse(String value) { + try { + Date dt = dateFormat.parse(value); + return elProp.parseDateTime(dt.getTime()); + + } catch (ParseException e) { + throw new TextException("Error parsing [" + value + "] using format[" + format + "]", e); + } + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/csv/package-info.java b/src/main/java/com/avaje/ebeaninternal/server/text/csv/package-info.java new file mode 100644 index 000000000..fee5b34ab --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/csv/package-info.java @@ -0,0 +1,4 @@ +/** + * CSV implementation. + */ +package com.avaje.ebeaninternal.server.text.csv; \ No newline at end of file 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 new file mode 100644 index 000000000..7b961a0b4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java @@ -0,0 +1,326 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebean.text.json.JsonContext; +import com.avaje.ebean.text.json.JsonElement; +import com.avaje.ebean.text.json.JsonReadOptions; +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebean.text.json.JsonWriteOptions; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.type.EscapeJson; +import com.avaje.ebeaninternal.util.ParamTypeHelper; +import com.avaje.ebeaninternal.util.ParamTypeHelper.ManyType; +import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo; + +/** + * Default implementation of JsonContext. + * + * @author rbygrave + */ +public class DJsonContext implements JsonContext { + + private final SpiEbeanServer server; + + private final JsonValueAdapter dfltValueAdapter; + + private final boolean dfltPretty; + + public DJsonContext(SpiEbeanServer server, JsonValueAdapter dfltValueAdapter, boolean dfltPretty){ + this.server = server; + this.dfltValueAdapter = dfltValueAdapter; + this.dfltPretty = dfltPretty; + } + + public boolean isSupportedType(Type genericType) { + return server.isSupportedType(genericType); + } + + private ReadJsonSource createReader(Reader jsonReader) { + return new ReadJsonSourceReader(jsonReader, 256, 512); + } + + public T toBean(Class cls, String json){ + return toBean(cls, new ReadJsonSourceString(json), null); + } + + public T toBean(Class cls, Reader jsonReader) { + return toBean(cls, createReader(jsonReader), null); + } + + public T toBean(Class cls, String json, JsonReadOptions options){ + return toBean(cls, new ReadJsonSourceString(json), options); + } + + public T toBean(Class cls, Reader jsonReader, JsonReadOptions options) { + return toBean(cls, createReader(jsonReader), options); + } + + private T toBean(Class cls, ReadJsonSource src, JsonReadOptions options){ + + BeanDescriptor d = getDecriptor(cls); + ReadJsonContext ctx = new ReadJsonContext(src, dfltValueAdapter, options); + return d.jsonReadBean(ctx, null); + } + + public List toList(Class cls, String json){ + return toList(cls, new ReadJsonSourceString(json), null); + } + + public List toList(Class cls, String json, JsonReadOptions options){ + return toList(cls, new ReadJsonSourceString(json), options); + } + + public List toList(Class cls, Reader jsonReader){ + return toList(cls, createReader(jsonReader), null); + } + + public List toList(Class cls, Reader jsonReader, JsonReadOptions options){ + return toList(cls, createReader(jsonReader), options); + } + + private List toList(Class cls, ReadJsonSource src, JsonReadOptions options){ + + try { + BeanDescriptor d = getDecriptor(cls); + + List list = new ArrayList(); + + ReadJsonContext ctx = new ReadJsonContext(src, dfltValueAdapter, options); + ctx.readArrayBegin(); + do { + T bean = d.jsonReadBean(ctx, null); + if (bean != null){ + list.add(bean); + } + if (!ctx.readArrayNext()){ + break; + } + } while(true); + + return list; + } catch (RuntimeException e){ + throw new TextException("Error parsing "+src, e); + } + } + + + public Object toObject(Type genericType, String json, JsonReadOptions options) { + + TypeInfo info = ParamTypeHelper.getTypeInfo(genericType); + Class beanType = info.getBeanType(); + if (JsonElement.class.isAssignableFrom(beanType)){ + return InternalJsonParser.parse(json); + } + + ManyType manyType = info.getManyType(); + switch (manyType) { + case NONE: + return toBean(info.getBeanType(), json, options); + + case LIST: + return toList(info.getBeanType(), json, options); + + default: + String msg = "ManyType "+manyType+" not supported yet"; + throw new TextException(msg); + } + } + + public Object toObject(Type genericType, Reader json, JsonReadOptions options) { + + TypeInfo info = ParamTypeHelper.getTypeInfo(genericType); + Class beanType = info.getBeanType(); + if (JsonElement.class.isAssignableFrom(beanType)){ + return InternalJsonParser.parse(json); + } + + ManyType manyType = info.getManyType(); + switch (manyType) { + case NONE: + return toBean(info.getBeanType(), json, options); + + case LIST: + return toList(info.getBeanType(), json, options); + + default: + String msg = "ManyType "+manyType+" not supported yet"; + throw new TextException(msg); + } + } + + + public void toJsonWriter(Object o, Writer writer) { + toJsonWriter(o, writer, dfltPretty, null, null); + } + + public void toJsonWriter(Object o, Writer writer, boolean pretty) { + toJsonWriter(o, writer, pretty, null, null); + } + + public void toJsonWriter(Object o, Writer writer, boolean pretty, JsonWriteOptions options){ + toJsonWriter(o, writer, pretty, null, null); + } + + public void toJsonWriter(Object o, Writer writer, boolean pretty, JsonWriteOptions options, String callback) { + toJsonInternal(o, new WriteJsonBufferWriter(writer), pretty, options, callback); + } + + public String toJsonString(Object o){ + return toJsonString(o, dfltPretty, null); + } + + public String toJsonString(Object o, boolean pretty){ + return toJsonString(o, pretty, null); + } + + public String toJsonString(Object o, boolean pretty, JsonWriteOptions options){ + return toJsonString(o, pretty, options, null); + } + + public String toJsonString(Object o, boolean pretty, JsonWriteOptions options, String callback){ + WriteJsonBufferString b = new WriteJsonBufferString(); + toJsonInternal(o, b, pretty, options, callback); + return b.getBufferOutput(); + } + + @SuppressWarnings("unchecked") + private void toJsonInternal(Object o, WriteJsonBuffer buffer, boolean pretty, JsonWriteOptions options, String requestCallback){ + + if (o == null){ + buffer.append("null"); + } else if (o instanceof Number) { + buffer.append(o.toString()); + } else if (o instanceof Boolean) { + buffer.append(o.toString()); + } else if (o instanceof String) { + EscapeJson.escapeQuote(o.toString(), buffer); + } else if (o instanceof JsonElement) { + + } else if (o instanceof Map){ + toJsonFromMap((Map)o, buffer, pretty, options, requestCallback); + + } else if (o instanceof Collection){ + toJsonFromCollection((Collection)o, buffer, pretty, options, requestCallback); + + } else { + BeanDescriptor d = getDecriptor(o.getClass()); + WriteJsonContext ctx = new WriteJsonContext(buffer, pretty, dfltValueAdapter, options, requestCallback); + d.jsonWrite(ctx, o); + ctx.end(); + } + } + + + private void toJsonFromCollection(Collection c, WriteJsonBuffer buffer, boolean pretty, + JsonWriteOptions options, String requestCallback){ + + Iterator it = c.iterator(); + if (!it.hasNext()){ + buffer.append("[]"); + return; + } + + WriteJsonContext ctx = new WriteJsonContext(buffer, pretty, dfltValueAdapter, options, requestCallback); + + Object o = it.next(); + BeanDescriptor d = getDecriptor(o.getClass()); + + ctx.appendArrayBegin(); + d.jsonWrite(ctx, o); + while (it.hasNext()) { + ctx.appendComma(); + T t = it.next(); + d.jsonWrite(ctx, t); + } + ctx.appendArrayEnd(); + ctx.end(); + } + + private void toJsonFromMap(Map map, WriteJsonBuffer buffer, boolean pretty, + JsonWriteOptions options, String requestCallback){ + + if (map.isEmpty()){ + buffer.append("{}"); + return; + } + + WriteJsonContext ctx = new WriteJsonContext(buffer, pretty, dfltValueAdapter, options, requestCallback); + + Set> entrySet = map.entrySet(); + Iterator> it = entrySet.iterator(); + + Entry entry = it.next(); + + ctx.appendObjectBegin(); + toJsonMapKey(buffer, false, entry.getKey()); + toJsonMapValue(buffer, pretty, options, requestCallback, entry.getValue()); + + while (it.hasNext()) { + entry = it.next(); + ctx.appendComma(); + toJsonMapKey(buffer, pretty, entry.getKey()); + toJsonMapValue(buffer, pretty, options, requestCallback, entry.getValue()); + } + ctx.appendObjectEnd(); + ctx.end(); + } + + private void toJsonMapKey(WriteJsonBuffer buffer, boolean pretty, Object key) { + if (pretty){ + buffer.append("\n"); + } + buffer.append("\""); + buffer.append(key.toString()); + buffer.append("\":"); + } + + private void toJsonMapValue(WriteJsonBuffer buffer, boolean pretty, JsonWriteOptions options, String requestCallback, + Object value) { + + if (value == null){ + buffer.append("null"); + } else { + toJsonInternal(value, buffer, pretty, options, requestCallback); + } + } + + private BeanDescriptor getDecriptor(Class cls) { + BeanDescriptor d = server.getBeanDescriptor(cls); + if (d == null){ + String msg = "No BeanDescriptor found for "+cls; + throw new RuntimeException(msg); + } + return d; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DefaultJsonValueAdapter.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DefaultJsonValueAdapter.java new file mode 100644 index 000000000..48c1f0b55 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DefaultJsonValueAdapter.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import java.sql.Date; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +import com.avaje.ebean.text.json.JsonValueAdapter; + +public class DefaultJsonValueAdapter implements JsonValueAdapter { + + private final SimpleDateFormat dateTimeProto; + + public DefaultJsonValueAdapter(String dateTimeFormat){ + this.dateTimeProto = new SimpleDateFormat(dateTimeFormat); + this.dateTimeProto.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + public DefaultJsonValueAdapter(){ + this("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + } + + private SimpleDateFormat dtFormat() { + return (SimpleDateFormat)dateTimeProto.clone(); + } + + public String jsonFromDate(Date date) { + return "\""+date.toString()+"\""; + } + + public String jsonFromTimestamp(Timestamp date) { + return "\""+dtFormat().format(date)+"\""; + } + + public Date jsonToDate(String jsonDate) { + return Date.valueOf(jsonDate); + } + + public Timestamp jsonToTimestamp(String jsonDateTime) { + try { + java.util.Date d = dtFormat().parse(jsonDateTime); + return new Timestamp(d.getTime()); + } catch (Exception e) { + String m = "Error parsing Datetime["+jsonDateTime+"]"; + throw new RuntimeException(m, e); + } + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/InternalJsonParser.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/InternalJsonParser.java new file mode 100644 index 000000000..f767fac17 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/InternalJsonParser.java @@ -0,0 +1,23 @@ +package com.avaje.ebeaninternal.server.text.json; + +import java.io.Reader; + +import com.avaje.ebean.text.json.JsonElement; + +public class InternalJsonParser { + + public static JsonElement parse(String s) { + + ReadJsonSourceString src = new ReadJsonSourceString(s); + ReadBasicJsonContext b = new ReadBasicJsonContext(src); + return ReadJsonRawReader.readJsonElement(b); + } + + public static JsonElement parse(Reader s) { + + ReadJsonSourceReader src = new ReadJsonSourceReader(s, 512, 256); + ReadBasicJsonContext b = new ReadBasicJsonContext(src); + return ReadJsonRawReader.readJsonElement(b); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/PathStack.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/PathStack.java new file mode 100644 index 000000000..74743b9ec --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/PathStack.java @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import com.avaje.ebeaninternal.server.util.ArrayStack; + +public class PathStack extends ArrayStack { + + public String peekFullPath(String key){ + + String prefix = peekWithNull(); + if (prefix != null){ + return prefix+"."+key; + } else { + return key; + } + } + + public void pushPathKey(String key) { + + String prefix = peekWithNull(); + if (prefix != null){ + key = prefix+"."+key; + } + push(key); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadBasicJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadBasicJsonContext.java new file mode 100644 index 000000000..8177cd130 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadBasicJsonContext.java @@ -0,0 +1,269 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import com.avaje.ebean.text.TextException; + +public class ReadBasicJsonContext implements ReadJsonInterface { + + private final ReadJsonSource src; + + private char tokenStart; + private String tokenKey; + + public ReadBasicJsonContext(ReadJsonSource src) { + this.src = src; + } + + public char getToken() { + return tokenStart; + } + + public String getTokenKey() { + return tokenKey; + } + + public boolean isTokenKey() { + return '\"' == tokenStart; + } + + public boolean isTokenObjectEnd() { + return '}' == tokenStart; + } + + public boolean readObjectBegin() { + readNextToken(); + if ('{' == tokenStart){ + return true; + } else if ('n' == tokenStart) { + return false; + } else if (']' == tokenStart) { + // an empty array + return false; + } + throw new RuntimeException("Expected object begin at "+src.getErrorHelp()); + } + + public boolean readKeyNext() { + readNextToken(); + if ('\"' == tokenStart){ + return true; + } else if ('}' == tokenStart) { + return false; + } + throw new RuntimeException("Expected '\"' or '}' at "+src.getErrorHelp()); + } + + public boolean readValueNext() { + readNextToken(); + if (',' == tokenStart){ + return true; + } else if ('}' == tokenStart) { + return false; + } + throw new RuntimeException("Expected ',' or '}' at "+src.getErrorHelp()+" but got "+tokenStart); + } + + public boolean readArrayBegin() { + readNextToken(); + if ('[' == tokenStart){ + return true; + } else if ('n' == tokenStart) { + return false; + } + throw new RuntimeException("Expected array begin at "+src.getErrorHelp()); + } + + public boolean readArrayNext() { + readNextToken(); + if (',' == tokenStart){ + return true; + } + if (']' == tokenStart){ + return false; + } + throw new RuntimeException("Expected ',' or ']' at "+src.getErrorHelp()); + } + + public void readNextToken() { + + ignoreWhiteSpace(); + + tokenStart = src.nextChar("EOF finding next token"); + switch (tokenStart) { + case '"': + internalReadKey(); + break; + case '{': break; + case '}': break; + case '[': break; // not expected + case ']': break; // not expected + case ',': break; // not expected + case ':': break; // not expected + case 'n': + internalReadNull(); + break; // not expected + + default: + throw new RuntimeException("Unexpected tokenStart["+tokenStart+"] "+src.getErrorHelp()); + } + + } + + public String readQuotedValue() { + + boolean escape = false; + StringBuilder sb = new StringBuilder(); + + do { + char ch = src.nextChar("EOF reading quoted value"); + if (escape) { + // in escape mode so just append the character + escape = false; + switch (ch) { + case 'n': + sb.append('\n'); + break; + case 'r': + sb.append('\r'); + break; + case 't': + sb.append('\t'); + break; + case 'f': + sb.append('\f'); + break; + case 'b': + sb.append('\b'); + break; + case '"': + sb.append('"'); + break; + + default: + sb.append('\\'); + sb.append(ch); + break; + } + + } else { + switch (ch) { + case '\\': + // put into 'escape' mode for next character + escape = true; + break; + case '"': + return sb.toString(); + + default: + sb.append(ch); + } + } + } while (true); + } + + public String readUnquotedValue(char c) { + String v = readUnquotedValueRaw(c); + if ("null".equals(v)){ + return null; + } else { + return v; + } + } + + private String readUnquotedValueRaw(char c) { + + StringBuilder sb = new StringBuilder(); + sb.append(c); + + do { + tokenStart = src.nextChar("EOF reading unquoted value"); + switch (tokenStart) { + case ',': + src.back(); + return sb.toString(); + + case '}': + src.back(); + return sb.toString(); + + case ' ': + return sb.toString(); + + case '\t': + return sb.toString(); + + case '\r': + return sb.toString(); + + case '\n': + return sb.toString(); + + default: + sb.append(tokenStart); + } + + } while (true); + + } + + private void internalReadNull() { + + StringBuilder sb = new StringBuilder(4); + sb.append(tokenStart); + for (int i = 0; i < 3; i++) { + char c = src.nextChar("EOF reading null "); + sb.append(c); + } + if (!"null".equals(sb.toString())){ + throw new TextException("Expected 'null' but got "+sb.toString()+" "+src.getErrorHelp()); + } + } + + private void internalReadKey() { + StringBuilder sb = new StringBuilder(); + do { + char c = src.nextChar("EOF reading key"); + if ('\"' == c){ + tokenKey = sb.toString(); + break; + } else { + sb.append(c); + } + } while (true); + + ignoreWhiteSpace(); + + char c = src.nextChar("EOF reading ':'"); + if (':' != c){ + throw new TextException("Expected to find colon after key at "+(src.pos()-1)+" but found ["+c+"]"+src.getErrorHelp()); + } + } + + public void ignoreWhiteSpace() { + src.ignoreWhiteSpace(); + } + + public char nextChar() { + tokenStart = src.nextChar("EOF getting nextChar for raw json"); + return tokenStart; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonContext.java new file mode 100644 index 000000000..7b6f11099 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonContext.java @@ -0,0 +1,201 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.text.json.JsonElement; +import com.avaje.ebean.text.json.JsonReadBeanVisitor; +import com.avaje.ebean.text.json.JsonReadOptions; +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.util.ArrayStack; + +public class ReadJsonContext extends ReadBasicJsonContext { + + private final Map> visitorMap; + + private final JsonValueAdapter valueAdapter; + + private final PathStack pathStack; + + private final ArrayStack beanState; + private ReadBeanState currentState; + + public ReadJsonContext(ReadJsonSource src, JsonValueAdapter dfltValueAdapter, JsonReadOptions options) { + super(src); + this.beanState = new ArrayStack(); + if (options == null){ + this.valueAdapter = dfltValueAdapter; + this.visitorMap = null; + this.pathStack = null; + } else { + this.valueAdapter = getValueAdapter(dfltValueAdapter, options.getValueAdapter()); + this.visitorMap = options.getVisitorMap(); + this.pathStack = (visitorMap == null || visitorMap.isEmpty()) ? null : new PathStack(); + } + } + + private JsonValueAdapter getValueAdapter(JsonValueAdapter dfltValueAdapter, JsonValueAdapter valueAdapter) { + return valueAdapter == null ? dfltValueAdapter : valueAdapter; + } + + public JsonValueAdapter getValueAdapter() { + return valueAdapter; + } + + public String readScalarValue() { + + ignoreWhiteSpace(); + + char prevChar = nextChar();//"EOF reading scalarValue?"); + if ('"' == prevChar){ + return readQuotedValue(); + } else { + return readUnquotedValue(prevChar); + } + } + + public void pushBean(Object bean, String path, BeanDescriptor beanDescriptor){ + currentState = new ReadBeanState(bean, beanDescriptor); + beanState.push(currentState); + if (pathStack != null){ + pathStack.pushPathKey(path); + } + } + + public ReadBeanState popBeanState() { + if (pathStack != null){ + String path = pathStack.peekWithNull(); + JsonReadBeanVisitor beanVisitor = visitorMap.get(path); + if (beanVisitor != null){ + currentState.visit(beanVisitor); + } + pathStack.pop(); + } + + // return the current ReadBeanState as we can't call setLoadedState() + // yet. We might bind master/detail beans together via mappedBy property + // so wait until after that before calling ReadBeanStatesetLoadedState(); + ReadBeanState s = currentState; + + beanState.pop(); + currentState = beanState.peekWithNull(); + return s; + } + + public void setProperty(String propertyName){ + currentState.setLoaded(propertyName); + } + + /** + * Got a key that doesn't map to a known property so read the json value + * which could be json primitive, object or array. + *

    + * Provide these values to a JsonReadBeanVisitor if registered. + *

    + */ + public JsonElement readUnmappedJson(String key) { + + JsonElement rawJsonValue = ReadJsonRawReader.readJsonElement(this); + if (visitorMap != null){ + currentState.addUnmappedJson(key, rawJsonValue); + } + return rawJsonValue; + } + + public static class ReadBeanState implements PropertyChangeListener { + + private final Object bean; + private final BeanDescriptor beanDescriptor; + private final EntityBeanIntercept ebi; + private final Set loadedProps; + private Map unmapped; + + private ReadBeanState(Object bean, BeanDescriptor beanDescriptor) { + this.bean = bean; + this.beanDescriptor = beanDescriptor; + if (bean instanceof EntityBean){ + this.ebi = ((EntityBean)bean)._ebean_getIntercept(); + this.loadedProps = new HashSet(); + } else { + this.ebi = null; + this.loadedProps = null; + } + } + public String toString(){ + return bean.getClass().getSimpleName()+" loaded:"+loadedProps; + } + + /** + * Add a loaded/set property to the set of loadedProps. + */ + public void setLoaded(String propertyName){ + if (ebi != null){ + loadedProps.add(propertyName); + } + } + + private void addUnmappedJson(String key, JsonElement value){ + if (unmapped == null){ + unmapped = new LinkedHashMap(); + } + unmapped.put(key, value); + } + + @SuppressWarnings("unchecked") + private void visit(JsonReadBeanVisitor beanVisitor) { + // listen for property change events so that + // we can update the loadedProps if necessary + if (ebi != null){ + ebi.addPropertyChangeListener(this); + } + beanVisitor.visit((T)bean, unmapped); + if (ebi != null){ + ebi.removePropertyChangeListener(this); + } + } + + public void setLoadedState(){ + if (ebi != null){ + // takes into account reference beans + beanDescriptor.setLoadedProps(ebi, loadedProps); + } + } + + public void propertyChange(PropertyChangeEvent evt) { + String propName = evt.getPropertyName(); + loadedProps.add(propName); + } + + public Object getBean() { + return bean; + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonInterface.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonInterface.java new file mode 100644 index 000000000..150ca475d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonInterface.java @@ -0,0 +1,22 @@ +package com.avaje.ebeaninternal.server.text.json; + +public interface ReadJsonInterface { + + public void ignoreWhiteSpace(); + + public char nextChar(); + + public String getTokenKey(); + + public boolean readKeyNext(); + + public boolean readValueNext(); + + public boolean readArrayNext(); + + public String readQuotedValue(); + + public String readUnquotedValue(char c); + + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonRawReader.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonRawReader.java new file mode 100644 index 000000000..78448b24a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonRawReader.java @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import com.avaje.ebean.text.json.JsonElementArray; +import com.avaje.ebean.text.json.JsonElementBoolean; +import com.avaje.ebean.text.json.JsonElementNull; +import com.avaje.ebean.text.json.JsonElementNumber; +import com.avaje.ebean.text.json.JsonElementObject; +import com.avaje.ebean.text.json.JsonElementString; +import com.avaje.ebean.text.json.JsonElement; + + + +public class ReadJsonRawReader { + + public static JsonElement readJsonElement(ReadJsonInterface ctx) { + return new ReadJsonRawReader(ctx).readJsonElement(); + } + + private final ReadJsonInterface ctx; + + private ReadJsonRawReader(ReadJsonInterface ctx){ + this.ctx = ctx; + } + + private JsonElement readJsonElement() { + return readValue(); + } + + private JsonElement readValue() { + + ctx.ignoreWhiteSpace(); + + char c = ctx.nextChar(); + + switch (c) { + case '{': + return readObject(); + + case '[': + return readArray(); + + case '"': + return readString(); + + default: + return readUnquoted(c); + } + } + + private JsonElement readArray() { + + JsonElementArray a = new JsonElementArray(); + + do { + JsonElement value = readValue(); + a.add(value); + if (!ctx.readArrayNext()){ + break; + } + } while(true); + + return a; + } + + private JsonElement readObject() { + + JsonElementObject o = new JsonElementObject(); + + do { + if (!ctx.readKeyNext()){ + break; + } else { + // we read a property key ... + String key = ctx.getTokenKey(); + JsonElement value = readValue(); + + o.put(key, value); + + if (!ctx.readValueNext()){ + break; + } + } + } while(true); + + return o; + } + + private JsonElement readString() { + String s = ctx.readQuotedValue(); + return new JsonElementString(s); + } + + private JsonElement readUnquoted(char c) { + String s = ctx.readUnquotedValue(c); + if ("null".equals(s)){ + return JsonElementNull.NULL; + + } else if ("true".equals(s)){ + return JsonElementBoolean.TRUE; + + } else if ("false".equals(s)) { + return JsonElementBoolean.FALSE; + + } + return new JsonElementNumber(s); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSource.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSource.java new file mode 100644 index 000000000..c1d71e65c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSource.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +public interface ReadJsonSource { + + public char nextChar(String eofMsg); + + public void ignoreWhiteSpace(); + + public void back(); + + public int pos(); + + public String getErrorHelp(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceReader.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceReader.java new file mode 100644 index 000000000..6e1236492 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceReader.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; + +import com.avaje.ebean.text.TextException; + +public class ReadJsonSourceReader implements ReadJsonSource { + + private final Reader reader; + + private char[] localBuffer; + + private int totalPos; + private int localPos; + private int localPosEnd; + + public ReadJsonSourceReader(Reader reader, int localBufferSize, int bufferSize) { + this.reader = new BufferedReader(reader,bufferSize); + this.localBuffer = new char[localBufferSize]; + } + + public String toString() { + return String.valueOf(localBuffer); + } + + + + public String getErrorHelp() { + int prev = localPos - 30; + if (prev < 0){ + prev = 0; + } + String c = new String(localBuffer, prev, (localPos-prev)); + return "pos:"+pos()+" preceding:"+c; + } + + public int pos() { + return totalPos+localPos; + } + + + public void ignoreWhiteSpace() { + do { + char c = nextChar("EOF ignoring whitespace"); + if (!Character.isWhitespace(c)){ + --localPos; + break; + } + } while(true); + } + + public void back() { + localPos--; + } + + public char nextChar(String eofMsg) { + if (localPos >= localPosEnd){ + if (!loadLocalBuffer()) { + throw new TextException(eofMsg+" at pos:"+(totalPos+localPos)); + } + } + return localBuffer[localPos++]; + } + + private boolean loadLocalBuffer() { + try { + localPosEnd = reader.read(localBuffer); + if (localPosEnd > 0){ + totalPos += localPos; + localPos = 0; + return true; + } else { + this.localBuffer = null; + return false; + } + + } catch (IOException e){ + throw new TextException(e); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceString.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceString.java new file mode 100644 index 000000000..f0bf55d4d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJsonSourceString.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import com.avaje.ebean.text.TextException; + +public class ReadJsonSourceString implements ReadJsonSource { + + private final String source; + private final int sourceLength; + private int pos; + + public ReadJsonSourceString(String source){ + this.source = source; + this.sourceLength = source.length(); + } + + public String getErrorHelp() { + int prev = pos - 50; + if (prev < 0){ + prev = 0; + } + String c = source.substring(prev, pos); + return "pos:"+pos+" precedingcontent:"+c; + } + + public String toString() { + return source; + } + + public int pos() { + return pos; + } + + public void back() { + pos--; + } + + public char nextChar(String eofMsg) { + if (pos >= sourceLength){ + throw new TextException(eofMsg+" at pos:"+pos); + } + return source.charAt(pos++); + } + + public void ignoreWhiteSpace() { + do { + char c = source.charAt(pos); + if (Character.isWhitespace(c)){ + ++pos; + } else { + break; + } + } while(true); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBuffer.java new file mode 100644 index 000000000..3635c4287 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBuffer.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + + +public interface WriteJsonBuffer extends Appendable { + + public WriteJsonBuffer append(String content); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferString.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferString.java new file mode 100644 index 000000000..e6cba51b7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferString.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import java.io.IOException; + +public class WriteJsonBufferString implements WriteJsonBuffer { + + private final StringBuilder buffer; + + public WriteJsonBufferString(){ + this.buffer = new StringBuilder(256); + } + + public WriteJsonBufferString append(CharSequence csq) throws IOException { + buffer.append(csq); + return this; + } + + public WriteJsonBufferString append(CharSequence csq, int start, int end) throws IOException { + buffer.append(csq, start, end); + return this; + } + + public WriteJsonBufferString append(char c) throws IOException { + buffer.append(c); + return this; + } + + public WriteJsonBufferString append(String content){ + buffer.append(content); + return this; + } + + public String getBufferOutput() { + return buffer.toString(); + } + + public String toString() { + return buffer.toString(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferWriter.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferWriter.java new file mode 100644 index 000000000..467a3d5c9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonBufferWriter.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import java.io.IOException; +import java.io.Writer; + +import com.avaje.ebean.text.TextException; + +public class WriteJsonBufferWriter implements WriteJsonBuffer { + + private final Writer buffer; + + public WriteJsonBufferWriter(Writer buffer){ + this.buffer = buffer; + } + + public WriteJsonBufferWriter append(String content){ + try { + buffer.write(content); + return this; + } catch (IOException e) { + throw new TextException(e); + } + } + + public WriteJsonBufferWriter append(CharSequence csq) throws IOException { + return append(csq, 0, csq.length()); + } + + public WriteJsonBufferWriter append(CharSequence csq, int start, int end) throws IOException { + for (int i = start; i < end; i++) { + buffer.append(csq.charAt(i)); + } + return this; + } + + public WriteJsonBufferWriter append(char c) throws IOException { + try { + buffer.write(c); + return this; + } catch (IOException e) { + throw new TextException(e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonContext.java new file mode 100644 index 000000000..4f73acca3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJsonContext.java @@ -0,0 +1,395 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.text.json; + +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebean.text.json.JsonWriteBeanVisitor; +import com.avaje.ebean.text.json.JsonWriteOptions; +import com.avaje.ebean.text.json.JsonWriter; +import com.avaje.ebeaninternal.server.type.EscapeJson; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.util.ArrayStack; + + +public class WriteJsonContext implements JsonWriter { + + private final WriteJsonBuffer buffer; + + private final boolean pretty; + + private final JsonValueAdapter valueAdapter; + + private final ArrayStack parentBeans = new ArrayStack(); + + private final PathProperties pathProperties; + + private final Map> visitorMap; + + private final String callback; + + private final PathStack pathStack; + + private WriteBeanState beanState; + + private int depthOffset; + + boolean assocOne; + + public WriteJsonContext(WriteJsonBuffer buffer, boolean pretty, JsonValueAdapter dfltValueAdapter, + JsonWriteOptions options, String requestCallback){ + + this.buffer = buffer; + this.pretty = pretty; + this.pathStack = new PathStack(); + this.callback = getCallback(requestCallback, options); + if (options == null){ + this.valueAdapter = dfltValueAdapter; + this.visitorMap = null; + this.pathProperties = null; + + } else { + this.valueAdapter = getValueAdapter(dfltValueAdapter, options.getValueAdapter()); + this.visitorMap = emptyToNull(options.getVisitorMap()); + this.pathProperties = emptyToNull(options.getPathProperties()); + } + + if (callback != null){ + buffer.append(requestCallback).append("("); + } + } + + public void appendRawValue(String key, String rawJsonValue) { + appendKeyWithComma(key, true); + buffer.append(rawJsonValue); + } + + public void appendQuoteEscapeValue(String key, String valueToEscape) { + appendKeyWithComma(key, true); + EscapeJson.escapeQuote(valueToEscape, buffer); + } + + public void end() { + if (callback != null){ + buffer.append(")"); + } + } + + private Map emptyToNull(Map m){ + if ( m == null || m.isEmpty()) { + return null; + } else { + return m; + } + } + + private PathProperties emptyToNull(PathProperties m){ + if ( m == null || m.isEmpty()) { + return null; + } else { + return m; + } + } + + private String getCallback(String requestCallback, JsonWriteOptions options) { + if (requestCallback != null){ + return requestCallback; + } + if (options != null){ + return options.getCallback(); + } + return null; + } + + private JsonValueAdapter getValueAdapter(JsonValueAdapter dfltValueAdapter, JsonValueAdapter valueAdapter) { + return valueAdapter == null ? dfltValueAdapter : valueAdapter; + } + + /** + * Return the set of properties to write to JSON. If null is returned then + * the default will output the properties loaded for this bean. + */ + public Set getIncludeProperties() { + if (pathProperties != null){ + String path = pathStack.peekWithNull(); + return pathProperties.get(path); + } + return null; + } + + public JsonWriteBeanVisitor getBeanVisitor() { + if (visitorMap != null){ + String path = pathStack.peekWithNull(); + return visitorMap.get(path); + } + return null; + } + + public String getJson() { + return buffer.toString(); + } + + private void appendIndent(){ + + buffer.append("\n"); + int depth = depthOffset + parentBeans.size(); + for (int i = 0; i < depth; i++) { + buffer.append(" "); + } + } + + public void appendObjectBegin(){ + if (pretty && !assocOne){ + appendIndent(); + } + buffer.append("{"); + } + public void appendObjectEnd(){ + buffer.append("}"); + } + + public void appendArrayBegin(){ + if (pretty){ + appendIndent(); + } + buffer.append("["); + depthOffset++; + } + + public void appendArrayEnd(){ + depthOffset--; + if (pretty){ + appendIndent(); + } + buffer.append("]"); + } + + public void appendComma(){ + buffer.append(","); + } + + public void addDepthOffset(int offset){ + depthOffset += offset; + } + + public void beginAssocOneIsNull(String key) { + depthOffset++; + internalAppendKeyBegin(key); + appendNull(); + depthOffset--; + } + + public void beginAssocOne(String key) { + pathStack.pushPathKey(key); + + internalAppendKeyBegin(key); + assocOne = true; + } + + public void endAssocOne() { + + pathStack.pop(); + assocOne = false; + } + + public Boolean includeMany(String key) { + if (pathProperties != null){ + String fullPath = pathStack.peekFullPath(key); + return pathProperties.hasPath(fullPath); + } + return null; + } + + public void beginAssocMany(String key) { + + pathStack.pushPathKey(key); + + depthOffset--; + internalAppendKeyBegin(key); + depthOffset++; + buffer.append("["); + } + + public void endAssocMany(){ + + pathStack.pop(); + + if (pretty){ + depthOffset--; + appendIndent(); + depthOffset++; + } + buffer.append("]"); + } + + private void internalAppendKeyBegin(String key) { + if (!beanState.isFirstKey()){ + buffer.append(","); + } + if (pretty){ + appendIndent(); + } + appendKeyWithComma(key, false); + } + + public void appendNameValue(String key, ScalarType scalarType, T value) { + appendKeyWithComma(key, true); + scalarType.jsonWrite(buffer, value, getValueAdapter()); + } + + public void appendDiscriminator(String key, String discValue) { + appendKeyWithComma(key, true); + buffer.append("\""); + buffer.append(discValue); + buffer.append("\""); + } + + private void appendKeyWithComma(String key, boolean withComma) { + if (withComma){ + if (!beanState.isFirstKey()){ + buffer.append(","); + } + } + buffer.append("\""); + if(key == null) { + buffer.append("null"); + } else { + buffer.append(key); + } + buffer.append("\":"); + } + + public void appendNull(String key) { + appendKeyWithComma(key, true); + buffer.append("null"); + } + + public void appendNull() { + buffer.append("null"); + } + + public JsonValueAdapter getValueAdapter() { + return valueAdapter; + } + + public String toString() { + return buffer.toString(); + } + + public void popParentBean(){ + parentBeans.pop(); + } + + public void pushParentBean(Object parentBean){ + parentBeans.push(parentBean); + } + + public void popParentBeanMany(){ + parentBeans.pop(); + depthOffset--; + } + + public void pushParentBeanMany(Object parentBean){ + parentBeans.push(parentBean); + depthOffset++; + } + + public boolean isParentBean(Object bean){ + if (parentBeans.isEmpty()){ + return false; + } else { + return parentBeans.contains(bean); + } + } + + public WriteBeanState pushBeanState(Object bean) { + WriteBeanState newState = new WriteBeanState(bean); + WriteBeanState prevState = beanState; + beanState = newState; + return prevState; + } + + public void pushPreviousState(WriteBeanState previousState) { + this.beanState = previousState; + } + + public boolean isReferenceBean() { + return beanState.isReferenceBean(); + } + + public boolean includedProp(String name) { + return beanState.includedProp(name); + } + + public Set getLoadedProps() { + return beanState.getLoadedProps(); + } + + + public static class WriteBeanState { + + private final EntityBeanIntercept ebi; + private final Set loadedProps; + private final boolean referenceBean; + private boolean firstKeyOut; + + public WriteBeanState(Object bean) { + if (bean instanceof EntityBean){ + this.ebi = ((EntityBean)bean)._ebean_getIntercept(); + this.loadedProps = ebi.getLoadedProps(); + this.referenceBean = ebi.isReference(); + } else { + this.ebi = null; + this.loadedProps = null; + this.referenceBean = false; + } + } + + public Set getLoadedProps() { + return loadedProps; + } + + public boolean includedProp(String name) { + if (loadedProps == null || loadedProps.contains(name)){ + return true; + } else { + return false; + } + } + public boolean isReferenceBean() { + return referenceBean; + } + + public boolean isFirstKey() { + if (!firstKeyOut){ + firstKeyOut = true; + return true; + } else { + return false; + } + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDelta.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDelta.java new file mode 100644 index 000000000..deb9e8dc2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDelta.java @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +public class BeanDelta { + + private final List properties; + + private final BeanDescriptor beanDescriptor; + + private final Object id; + + public BeanDelta(BeanDescriptor beanDescriptor, Object id) { + this.beanDescriptor = beanDescriptor; + this.id = id; + this.properties = new ArrayList(); + } + + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("BeanDelta["); + sb.append(beanDescriptor.getName()).append(":"); + sb.append(properties); + sb.append("]"); + return sb.toString(); + } + + public Object getId() { + return id; + } + + public void add(BeanProperty beanProperty, Object value) { + this.properties.add(new BeanDeltaProperty(beanProperty, value)); + } + + public void add(BeanDeltaProperty propertyDelta) { + this.properties.add(propertyDelta); + } + + public void apply(Object bean) { + + for (int i = 0; i < properties.size(); i++) { + properties.get(i).apply(bean); + } + } + + /** + * Read and return a BeanDelta from the binary input. + */ + public static BeanDelta readBinaryMessage(SpiEbeanServer server, DataInput dataInput) throws IOException { + + String descriptorId = dataInput.readUTF(); + BeanDescriptor desc = server.getBeanDescriptorById(descriptorId); + Object id = desc.getIdBinder().readData(dataInput); + BeanDelta bp = new BeanDelta(desc, id); + + int count = dataInput.readInt(); + for (int i = 0; i < count; i++) { + String propName = dataInput.readUTF(); + BeanProperty beanProperty = desc.getBeanProperty(propName); + Object value = beanProperty.getScalarType().readData(dataInput); + bp.add(beanProperty, value); + } + return bp; + } + + /** + * Write this bean delta in binary message format. + */ + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + BinaryMessage m = new BinaryMessage(50); + + DataOutputStream os = m.getOs(); + os.writeInt(BinaryMessage.TYPE_BEANDELTA); + os.writeUTF(beanDescriptor.getDescriptorId()); + + beanDescriptor.getIdBinder().writeData(os, id); + os.writeInt(properties.size()); + + for (int i = 0; i < properties.size(); i++) { + properties.get(i).writeBinaryMessage(m); + } + + os.flush(); + msgList.add(m); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaList.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaList.java new file mode 100644 index 000000000..d3f7293ce --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaList.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +public class BeanDeltaList { + + private final BeanDescriptor beanDescriptor; + + private final List deltaBeans = new ArrayList(); + + public BeanDeltaList(BeanDescriptor beanDescriptor) { + this.beanDescriptor = beanDescriptor; + } + + public String toString() { + return deltaBeans.toString(); + } + + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + public void add(BeanDelta b) { + deltaBeans.add(b); + } + + public List getDeltaBeans() { + return deltaBeans; + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + for (int i = 0; i < deltaBeans.size(); i++) { + deltaBeans.get(i).writeBinaryMessage(msgList); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaMap.java new file mode 100644 index 000000000..774a4a76a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaMap.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +public class BeanDeltaMap { + + private Map deltaMap = new HashMap(); + + public BeanDeltaMap() { + } + + public BeanDeltaMap(List deltaBeans) { + if (deltaBeans != null){ + for (int i = 0; i < deltaBeans.size(); i++) { + BeanDelta deltaBean = deltaBeans.get(i); + addBeanDelta(deltaBean); + } + } + } + + public String toString() { + return deltaMap.values().toString(); + } + + public void addBeanDelta(BeanDelta beanDelta){ + BeanDescriptor d = beanDelta.getBeanDescriptor(); + BeanDeltaList list = getDeltaBeanList(d); + list.add(beanDelta); + } + + public Collection deltaLists() { + return deltaMap.values(); + } + + private BeanDeltaList getDeltaBeanList(BeanDescriptor d) { + BeanDeltaList deltaList = deltaMap.get(d.getFullName()); + if (deltaList == null){ + deltaList = new BeanDeltaList(d); + deltaMap.put(d.getFullName(), deltaList); + } + return deltaList; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaProperty.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaProperty.java new file mode 100644 index 000000000..0ae45168f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanDeltaProperty.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.DataOutputStream; +import java.io.IOException; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +public class BeanDeltaProperty { + + private final BeanProperty beanProperty; + + private final Object value; + + public BeanDeltaProperty(BeanProperty beanProperty, Object value) { + this.beanProperty = beanProperty; + this.value = value; + } + + public String toString() { + return beanProperty.getName()+":"+value; + } + + public void apply(Object bean) { + beanProperty.setValue(bean, value); + } + + public void writeBinaryMessage(BinaryMessage m) throws IOException { + + DataOutputStream os = m.getOs(); + os.writeUTF(beanProperty.getName()); + beanProperty.getScalarType().writeData(os, value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdate.java new file mode 100644 index 000000000..b06c663c6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdate.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +public class BeanPathUpdate { + + private final Map map = new LinkedHashMap(); + + public void add(BeanDescriptor desc, String path, Object id) { + + String key = desc.getFullName()+":"+path; + BeanPathUpdateIds pathIds = map.get(key); + if (pathIds == null){ + pathIds = new BeanPathUpdateIds(desc, path); + map.put(key, pathIds); + } + pathIds.addId((Serializable)id); + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdateIds.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdateIds.java new file mode 100644 index 000000000..2a0080874 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPathUpdateIds.java @@ -0,0 +1,163 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; + +public class BeanPathUpdateIds { + + private transient BeanDescriptor beanDescriptor; + + private final String descriptorId; + + private String path; + + private ArrayList ids; + + /** + * Create the payload. + */ + public BeanPathUpdateIds(BeanDescriptor desc, String path) { + this.beanDescriptor = desc; + this.descriptorId = desc.getDescriptorId(); + this.path = path; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + if (beanDescriptor != null) { + sb.append(beanDescriptor.getFullName()); + } else { + sb.append("descId:").append(descriptorId); + } + sb.append(" path:").append(path); + sb.append(" ids:").append(ids); + return sb.toString(); + } + + public static BeanPathUpdateIds readBinaryMessage(SpiEbeanServer server, DataInput dataInput) throws IOException { + + String descriptorId = dataInput.readUTF(); + String path = dataInput.readUTF(); + BeanDescriptor desc = server.getBeanDescriptorById(descriptorId); + BeanPathUpdateIds bp = new BeanPathUpdateIds(desc, path); + bp.read(dataInput); + return bp; + } + + private void read(DataInput dataInput) throws IOException { + + IdBinder idBinder = beanDescriptor.getIdBinder(); + ids = readIdList(dataInput, idBinder); + } + + + private ArrayList readIdList(DataInput dataInput, IdBinder idBinder) throws IOException { + + int count = dataInput.readInt(); + if (count < 1) { + return null; + } + ArrayList idList = new ArrayList(count); + for (int i = 0; i < count; i++) { + Object id = idBinder.readData(dataInput); + idList.add((Serializable) id); + } + return idList; + } + + /** + * Write the contents into a BinaryMessage form. + *

    + * For a RemoteBeanPersist with a large number of id's note that this is + * broken up into many BinaryMessages each with a maximum of 100 ids. This + * enables the contents of a large RemoteTransactionEvent to be split up + * across multiple Packets. + *

    + */ + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + IdBinder idBinder = beanDescriptor.getIdBinder(); + + int count = ids == null ? 0 : ids.size(); + if (count > 0) { + int loop = 0; + int i = 0; + int eof = ids.size(); + do { + ++loop; + int endOfLoop = Math.min(eof, loop * 100); + + BinaryMessage m = new BinaryMessage(endOfLoop * 4 + 20); + + DataOutputStream os = m.getOs(); + os.writeInt(BinaryMessage.TYPE_BEANPATHUPDATE); + os.writeUTF(descriptorId); + os.writeUTF(path); + os.writeInt(count); + + for (; i < endOfLoop; i++) { + Serializable idValue = ids.get(i); + idBinder.writeData(os, idValue); + } + + os.flush(); + msgList.add(m); + + } while (i < eof); + } + } + + public void addId(Serializable id) { + ids.add(id); + } + + + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + /** + * Return the Descriptor Id. A more compact alternative to using the + * beanType. + */ + public String getDescriptorId() { + return descriptorId; + } + + public String getPath() { + return path; + } + + public List getIds() { + return ids; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIdMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIdMap.java new file mode 100644 index 000000000..24f9e060c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIdMap.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Organises the individual bean persist requests by type. + */ +public final class BeanPersistIdMap { + + private final Map beanMap = new LinkedHashMap(); + + public String toString() { + return beanMap.toString(); + } + + public boolean isEmpty() { + return beanMap.isEmpty(); + } + + public Collection values() { + return beanMap.values(); + } + + /** + * Add a Insert Update or Delete payload. + */ + public void add(BeanDescriptor desc, PersistRequest.Type type, Object id) { + + BeanPersistIds r = getPersistIds(desc); + r.addId(type, (Serializable)id); + } + + private BeanPersistIds getPersistIds(BeanDescriptor desc) { + String beanType = desc.getFullName(); + BeanPersistIds r = beanMap.get(beanType); + if (r == null){ + r = new BeanPersistIds(desc); + beanMap.put(beanType, r); + } + return r; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java new file mode 100644 index 000000000..3a826e0d5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java @@ -0,0 +1,307 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; + +/** + * Wraps the information representing a Inserted Updated or Deleted Bean. + *

    + * This information is broadcast across the cluster so that remote BeanListeners + * are notified of the inserts updates and deletes that occured. + *

    + *

    + * You control it the data is broadcast and what data is broadcast by the + * BeanListener.getClusterData() method. It is guessed that often just the Id + * property or perhaps a few properties in a Map will be broadcast to reduce the + * size of data sent around the network. + *

    + */ +public class BeanPersistIds implements Serializable { + + private static final long serialVersionUID = 8389469180931531409L; + + private transient BeanDescriptor beanDescriptor; + + private final String descriptorId; + + private ArrayList insertIds; + private ArrayList updateIds; + private ArrayList deleteIds; + + /** + * Create the payload. + */ + public BeanPersistIds(BeanDescriptor desc) { + this.beanDescriptor = desc; + this.descriptorId = desc.getDescriptorId(); + } + + public static BeanPersistIds readBinaryMessage(SpiEbeanServer server, DataInput dataInput) throws IOException { + + String descriptorId = dataInput.readUTF(); + BeanDescriptor desc = server.getBeanDescriptorById(descriptorId); + BeanPersistIds bp = new BeanPersistIds(desc); + bp.read(dataInput); + return bp; + } + + private void read(DataInput dataInput) throws IOException { + + IdBinder idBinder = beanDescriptor.getIdBinder(); + + int iudType = dataInput.readInt(); + ArrayList idList = readIdList(dataInput, idBinder); + + switch (iudType) { + case 0: + insertIds = idList; + break; + case 1: + updateIds = idList; + break; + case 2: + deleteIds = idList; + break; + + default: + throw new RuntimeException("Invalid iudType "+iudType); + } + } + + /** + * Write the contents into a BinaryMessage form. + *

    + * For a RemoteBeanPersist with a large number of id's note that this is + * broken up into many BinaryMessages each with a maximum of 100 ids. This + * enables the contents of a large RemoteTransactionEvent to be split up + * across multiple Packets. + *

    + */ + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + writeIdList(beanDescriptor, 0, insertIds, msgList); + writeIdList(beanDescriptor, 1, updateIds, msgList); + writeIdList(beanDescriptor, 2, deleteIds, msgList); + + } + + private ArrayList readIdList(DataInput dataInput, IdBinder idBinder) throws IOException { + + int count = dataInput.readInt(); + if (count < 1) { + return null; + } + ArrayList idList = new ArrayList(count); + for (int i = 0; i < count; i++) { + Object id = idBinder.readData(dataInput); + idList.add((Serializable) id); + } + return idList; + } + + /** + * Write a BinaryMessage containing the descriptorId, iudType and list of Id + * values. + *

    + * Note that a given BinaryMessage has a maximum of 100 Ids. This is due to + * the limit of UDP packet sizes. We break up the RemoteBeanPersist into + * potentially many smaller BinaryMessages which may be put into multiple + * Packets. + *

    + */ + private void writeIdList(BeanDescriptor desc, int iudType, ArrayList idList, + BinaryMessageList msgList) throws IOException { + + IdBinder idBinder = desc.getIdBinder(); + + int count = idList == null ? 0 : idList.size(); + if (count > 0) { + int loop = 0; + int i = 0; + int eof = idList.size(); + do { + ++loop; + int endOfLoop = Math.min(eof, loop * 100); + + BinaryMessage m = new BinaryMessage(endOfLoop * 4 + 20); + + DataOutputStream os = m.getOs(); + os.writeInt(BinaryMessage.TYPE_BEANIUD); + os.writeUTF(descriptorId); + os.writeInt(iudType); + os.writeInt(count); + + for (; i < endOfLoop; i++) { + Serializable idValue = idList.get(i); + idBinder.writeData(os, idValue); + } + + os.flush(); + msgList.add(m); + + } while (i < eof); + } + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + if (beanDescriptor != null) { + sb.append(beanDescriptor.getFullName()); + } else { + sb.append("descId:").append(descriptorId); + } + if (insertIds != null) { + sb.append(" insertIds:").append(insertIds); + } + if (updateIds != null) { + sb.append(" updateIds:").append(updateIds); + } + if (deleteIds != null) { + sb.append(" deleteIds:").append(deleteIds); + } + return sb.toString(); + } + + public void addId(PersistRequest.Type type, Serializable id) { + switch (type) { + case INSERT: + addInsertId(id); + break; + case UPDATE: + addUpdateId(id); + break; + case DELETE: + addDeleteId(id); + break; + + default: + break; + } + } + + private void addInsertId(Serializable id) { + if (insertIds == null) { + insertIds = new ArrayList(); + } + insertIds.add(id); + } + + private void addUpdateId(Serializable id) { + if (updateIds == null) { + updateIds = new ArrayList(); + } + updateIds.add(id); + } + + private void addDeleteId(Serializable id) { + if (deleteIds == null) { + deleteIds = new ArrayList(); + } + deleteIds.add(id); + } + + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + /** + * Return the Descriptor Id. A more compact alternative to using the + * beanType. + */ + public String getDescriptorId() { + return descriptorId; + } + + public List getInsertIds() { + return insertIds; + } + + public List getUpdateIds() { + return updateIds; + } + + public List getDeleteIds() { + return deleteIds; + } + + public void setBeanDescriptor(BeanDescriptor beanDescriptor) { + this.beanDescriptor = beanDescriptor; + } + + /** + * Notify the cache and local BeanPersistListener of this event that came + * from another server in the cluster. + */ + public void notifyCacheAndListener() { + + BeanPersistListener listener = beanDescriptor.getPersistListener(); + + // any change invalidates the query cache + beanDescriptor.queryCacheClear(); + + if (insertIds != null) { + if (listener != null) { + // notify listener + for (int i = 0; i < insertIds.size(); i++) { + listener.remoteInsert(insertIds.get(i)); + } + } + } + if (updateIds != null) { + for (int i = 0; i < updateIds.size(); i++) { + Serializable id = updateIds.get(i); + + // remove from cache + beanDescriptor.cacheRemove(id); + if (listener != null) { + // notify listener + listener.remoteInsert(id); + } + } + } + if (deleteIds != null) { + for (int i = 0; i < deleteIds.size(); i++) { + Serializable id = deleteIds.get(i); + + // remove from cache + beanDescriptor.cacheRemove(id); + if (listener != null) { + // notify listener + listener.remoteInsert(id); + } + } + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BulkEventListenerMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BulkEventListenerMap.java new file mode 100644 index 000000000..495fb3563 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BulkEventListenerMap.java @@ -0,0 +1,62 @@ +package com.avaje.ebeaninternal.server.transaction; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +import com.avaje.ebean.event.BulkTableEvent; +import com.avaje.ebean.event.BulkTableEventListener; + +public class BulkEventListenerMap { + + private final HashMap map = new HashMap(); + + public BulkEventListenerMap(List listeners) { + + if (listeners != null) { + for (BulkTableEventListener l : listeners) { + Set tables = l.registeredTables(); + for (String tableName : tables) { + register(tableName, l); + } + } + } + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public void process(BulkTableEvent event) { + + Entry entry = map.get(event.getTableName()); + if (entry != null){ + entry.process(event); + } + } + + private void register(String tableName, BulkTableEventListener l) { + String upperTableName = tableName.trim().toUpperCase(); + Entry entry = map.get(upperTableName); + if (entry == null){ + entry = new Entry(); + map.put(upperTableName, entry); + } + entry.add(l); + } + + private static class Entry { + + List listeners = new ArrayList(); + private void add(BulkTableEventListener l) { + listeners.add(l); + } + + private void process(BulkTableEvent event){ + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).process(event); + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java new file mode 100644 index 000000000..83a7c6dd2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -0,0 +1,183 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.Monitor; +import com.avaje.ebeaninternal.server.subclass.SubClassUtil; + +/** + * Default implementation of PersistenceContext. + *

    + * Ensures only one instance of a bean is used according to its type and unique + * id. + *

    + *

    + * PersistenceContext lives on a Transaction and as such is expected to only have + * a single thread accessing it at a time. This is not expected to be used concurrently. + *

    + *

    + * 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. + *

    + */ +public final class DefaultPersistenceContext implements PersistenceContext { + + /** + * Map used hold caches. One cache per bean type. + */ + private final HashMap typeCache = new HashMap(); + + private final Monitor monitor = new Monitor(); + + /** + * Create a new PersistanceContext. + */ + public DefaultPersistenceContext() { + } + + /** + * Set an object into the PersistanceContext. + */ + public void put(Object id, Object bean) { + synchronized (monitor) { + getClassContext(bean.getClass()).put(id, bean); + } + } + + public Object putIfAbsent(Object id, Object bean){ + synchronized (monitor) { + return getClassContext(bean.getClass()).putIfAbsent(id, bean); + } + } + + + + /** + * Return an object given its type and unique id. + */ + public Object get(Class beanType, Object id) { + synchronized (monitor) { + return getClassContext(beanType).get(id); + } + } + + /** + * Return the number of beans of the given type in the persistence context. + */ + public int size(Class beanType) { + synchronized (monitor) { + ClassContext classMap = typeCache.get(beanType.getName()); + return classMap == null ? 0 : classMap.size(); + } + } + + /** + * Clear the PersistenceContext. + */ + public void clear() { + synchronized (monitor) { + typeCache.clear(); + } + } + + public void clear(Class beanType) { + synchronized (monitor) { + ClassContext classMap = typeCache.get(beanType.getName()); + if (classMap != null) { + classMap.clear(); + } + } + } + + public void clear(Class beanType, Object id) { + synchronized (monitor) { + ClassContext classMap = typeCache.get(beanType.getName()); + if (classMap != null && id != null) { + //id = getUid(beanType, id); + classMap.remove(id); + } + } + } + + public String toString() { + synchronized (monitor) { + StringBuilder sb = new StringBuilder(); + Iterator> it = typeCache.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + if (entry.getValue().size() > 0){ + sb.append(entry.getKey()+":"+entry.getValue().size()+"; "); + } + } + return sb.toString(); + } + } + + private ClassContext getClassContext(Class beanType) { + + // strip off $$EntityBean.. suffix... + String clsName = SubClassUtil.getSuperClassName(beanType.getName()); + + ClassContext classMap = typeCache.get(clsName); + if (classMap == null) { + classMap = new ClassContext(); + typeCache.put(clsName, classMap); + } + return classMap; + } + + private static class ClassContext { + + private final WeakValueMap map = new WeakValueMap(); + + private Object get(Object id){ + return map.get(id); + } + + private Object putIfAbsent(Object id, Object bean){ + + return map.putIfAbsent(id, bean); + } + + private void put(Object id, Object b){ + map.put(id, b); + } + + private int size() { + return map.size(); + } + + private void clear(){ + map.clear(); + } + + private Object remove(Object id){ + return map.remove(id); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java new file mode 100644 index 000000000..edf20ff89 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java @@ -0,0 +1,45 @@ +package com.avaje.ebeaninternal.server.transaction; + +import com.avaje.ebeaninternal.api.SpiTransaction; + +/** + * Manages the transaction scoping using a Ebean thread local. + */ +public class DefaultTransactionScopeManager extends TransactionScopeManager { + + + public DefaultTransactionScopeManager(TransactionManager transactionManager){ + super(transactionManager); + } + + public void commit() { + DefaultTransactionThreadLocal.commit(serverName); + } + + public void end() { + DefaultTransactionThreadLocal.end(serverName); + } + + public SpiTransaction get() { + SpiTransaction t = DefaultTransactionThreadLocal.get(serverName); + if (t == null || !t.isActive()){ + return null; + } else { + return t; + } + } + + public void replace(SpiTransaction trans) { + DefaultTransactionThreadLocal.replace(serverName, trans); + } + + public void rollback() { + DefaultTransactionThreadLocal.rollback(serverName); + } + + public void set(SpiTransaction trans) { + DefaultTransactionThreadLocal.set(serverName, trans); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionThreadLocal.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionThreadLocal.java new file mode 100644 index 000000000..a70f2410a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultTransactionThreadLocal.java @@ -0,0 +1,147 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.transaction.TransactionMap.State; + +/** + * Used by EbeanMgr to store its Transactions in a ThreadLocal. This way the + * transaction objects don't have to passed around. + */ +public final class DefaultTransactionThreadLocal { + + private static ThreadLocal local = new ThreadLocal() { + protected synchronized TransactionMap initialValue() { + return new TransactionMap(); + } + }; + + /** + * Not allowed. + */ + private DefaultTransactionThreadLocal() { + } + + /** + * Return the current TransactionState for a given serverName. This is for the + * local thread of course. + */ + private static TransactionMap.State getState(String serverName) { + return local.get().getStateWithCreate(serverName); + } + + /** + * Set a new Transaction for this serverName and Thread. + */ + public static void set(String serverName, SpiTransaction trans) { + getState(serverName).set(trans); + } + + /** + * A mechanism to get the transaction out of the thread local by replacing it + * with a 'proxy'. + *

    + * Used for background fetching. Replaces the current transaction with a + * 'dummy' transaction. The current transaction is given to the background + * thread so it can continue the fetch. + *

    + */ + public static void replace(String serverName, SpiTransaction trans) { + getState(serverName).replace(trans); + } + + /** + * Return the current Transaction for this serverName and Thread. + */ + public static SpiTransaction get(String serverName) { + TransactionMap map = local.get(); + State state = map.getState(serverName); + SpiTransaction t = (state == null) ? null : state.transaction; + if (map.isEmpty()) { + local.remove(); + } + return t; + } + + /** + * Commit the current transaction. + */ + public static void commit(String serverName) { + TransactionMap map = local.get(); + State state = map.removeState(serverName); + if (state == null) { + throw new IllegalStateException("No current transaction for [" + serverName + "]"); + } + state.commit(); + if (map.isEmpty()) { + local.remove(); + } + } + + /** + * Rollback the current transaction. + */ + public static void rollback(String serverName) { + TransactionMap map = local.get(); + State state = map.removeState(serverName); + if (state == null) { + throw new IllegalStateException("No current transaction for [" + serverName + "]"); + } + state.rollback(); + if (map.isEmpty()) { + local.remove(); + } + } + + /** + * If the transaction has not been committed then roll it back. + *

    + * Designed to be put in a finally block instead of a rollback() in each catch + * block. + * + *

    +   * Ebean.beingTransaction();
    +   * try {
    +   *   // ... perform some actions in a single transaction
    +   * 
    +   *   Ebean.commitTransaction();
    +   * 
    +   * } finally {
    +   *   // ensure transaction ended. If some error occurred then rollback()
    +   *   Ebean.endTransaction();
    +   * }
    +   * 
    + * + *

    + */ + public static void end(String serverName) { + + TransactionMap map = local.get(); + State state = map.removeState(serverName); + if (state != null) { + state.end(); + } + if (map.isEmpty()) { + local.remove(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java new file mode 100644 index 000000000..03f3eb5a5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Beans deleted by Id used for updating L2 Cache and Lucene indexes. + */ +public final class DeleteByIdMap { + + private final Map beanMap = new LinkedHashMap(); + + public String toString() { + return beanMap.toString(); + } + + public void notifyCache() { + for (BeanPersistIds deleteIds : beanMap.values()) { + BeanDescriptor d = deleteIds.getBeanDescriptor(); + List idValues = deleteIds.getDeleteIds(); + if (idValues != null){ + d.queryCacheClear(); + for (int i = 0; i < idValues.size(); i++) { + d.cacheRemove(idValues.get(i)); + } + } + } + + } + + public boolean isEmpty() { + return beanMap.isEmpty(); + } + + public Collection values() { + return beanMap.values(); + } + + /** + * Add a Insert Update or Delete payload. + */ + public void add(BeanDescriptor desc, Object id) { + + BeanPersistIds r = getPersistIds(desc); + r.addId(PersistRequest.Type.DELETE, (Serializable)id); + } + + /** + * Add a List of Insert Update or Delete Id's. + */ + public void addList(BeanDescriptor desc, List idList) { + + BeanPersistIds r = getPersistIds(desc); + for (int i = 0; i < idList.size(); i++) { + r.addId(PersistRequest.Type.DELETE, (Serializable) idList.get(i)); + } + } + + private BeanPersistIds getPersistIds(BeanDescriptor desc) { + String beanType = desc.getFullName(); + BeanPersistIds r = beanMap.get(beanType); + if (r == null){ + r = new BeanPersistIds(desc); + beanMap.put(beanType, r); + } + return r; + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalJdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalJdbcTransaction.java new file mode 100644 index 000000000..dfdddc7ac --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalJdbcTransaction.java @@ -0,0 +1,93 @@ +package com.avaje.ebeaninternal.server.transaction; + +import java.sql.Connection; + +import javax.persistence.PersistenceException; +import javax.persistence.RollbackException; + +import com.avaje.ebean.LogLevel; + +/** + * Transaction based on a java.sql.Connection supplied by an external + * transaction manager such as Spring. + *

    + * This means that the transaction demarcation [commit(), rollback(), end()] + * must be controlled externally (by Spring etc) and so these methods [commit(), + * rollback(), end()] can not be called on this ExternalJdbcTransaction. + *

    + *

    + * That is, a transaction started externally (by Spring etc) must be committed + * or rolled back externally as well. + *

    + */ +public class ExternalJdbcTransaction extends JdbcTransaction { + + /** + * Create a Transaction that will have no transaction logging support. + *

    + * You need to create with a TransactionManager to have transaction logging. + *

    + */ + public ExternalJdbcTransaction(Connection connection) { + super(null, true, LogLevel.NONE, connection, null); + } + + /** + * Using the TransactionManager and its current LogLevel. + */ + public ExternalJdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) { + super(id, explicit, manager.getTransactionLogLevel(), connection, manager); + } + + /** + * Construct will all explicit parameters. + */ + public ExternalJdbcTransaction(String id, boolean explicit, LogLevel logLevel, Connection connection, TransactionManager manager) { + super(id, explicit, logLevel, connection, manager); + } + + /** + * This will always throw a PersistenceException. + *

    + * Externally created connections should be committed or rolled back externally. + *

    + */ + @Override + public void commit() throws RollbackException { + throw new PersistenceException("This is an external transaction so must be committed externally"); + } + + /** + * This will always throw a PersistenceException. + *

    + * Externally created connections should be committed or rolled back externally. + *

    + */ + @Override + public void end() throws PersistenceException { + throw new PersistenceException("This is an external transaction so must be committed externally"); + } + + /** + * This will always throw a PersistenceException. + *

    + * Externally created connections should be committed or rolled back externally. + *

    + */ + @Override + public void rollback() throws PersistenceException { + throw new PersistenceException("This is an external transaction so must be rolled back externally"); + } + + /** + * This will always throw a PersistenceException. + *

    + * Externally created connections should be committed or rolled back externally. + *

    + */ + @Override + public void rollback(Throwable e) throws PersistenceException { + throw new PersistenceException("This is an external transaction so must be rolled back externally"); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java new file mode 100644 index 000000000..1c5342d4b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009 the original author or authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import com.avaje.ebean.config.ExternalTransactionManager; +import com.avaje.ebeaninternal.api.SpiTransaction; + +/** + * A TransactionScopeManager aware of external transaction managers. + */ +public class ExternalTransactionScopeManager extends TransactionScopeManager { + + final ExternalTransactionManager externalManager; + + /** + * Instantiates transaction scope manager. + * + * @param transactionManager the transaction manager + */ + public ExternalTransactionScopeManager(TransactionManager transactionManager, ExternalTransactionManager externalManager) { + super(transactionManager); + this.externalManager = externalManager; + } + + public void commit() { + DefaultTransactionThreadLocal.commit(serverName); + } + + + public void end() { + DefaultTransactionThreadLocal.end(serverName); + } + + public SpiTransaction get() { + + return (SpiTransaction)externalManager.getCurrentTransaction(); + } + + public void replace(SpiTransaction trans) { + DefaultTransactionThreadLocal.replace(serverName, trans); + } + + public void rollback() { + DefaultTransactionThreadLocal.rollback(serverName); + } + + public void set(SpiTransaction trans) { + DefaultTransactionThreadLocal.set(serverName, trans); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/IndexEvent.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/IndexEvent.java new file mode 100644 index 000000000..557ff7330 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/IndexEvent.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public class IndexEvent { + + public static final int COMMIT_EVENT = 1; + public static final int OPTIMISE_EVENT = 2; + + private final int eventType; + private final String indexName; + + public IndexEvent(int eventType, String indexName) { + this.eventType = eventType; + this.indexName = indexName; + } + + public int getEventType() { + return eventType; + } + + public String getIndexName() { + return indexName; + } + + public static IndexEvent readBinaryMessage(DataInput dataInput) throws IOException { + + int eventType = dataInput.readInt(); + String indexName = dataInput.readUTF(); + return new IndexEvent(eventType, indexName); + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + BinaryMessage msg = new BinaryMessage(indexName.length()+10); + DataOutputStream os = msg.getOs(); + os.writeInt(BinaryMessage.TYPE_INDEX); + os.writeInt(eventType); + os.writeUTF(indexName); + + msgList.add(msg); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/IndexInvalidate.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/IndexInvalidate.java new file mode 100644 index 000000000..a4f3770c4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/IndexInvalidate.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.DataInput; +import java.io.DataOutputStream; +import java.io.IOException; + +import com.avaje.ebeaninternal.server.cluster.BinaryMessage; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public class IndexInvalidate { + + private final String indexName; + + public IndexInvalidate(String indexName) { + this.indexName = indexName; + } + + public String getIndexName() { + return indexName; + } + + @Override + public int hashCode() { + int hc = IndexInvalidate.class.hashCode(); + hc = hc * 31 + indexName.hashCode(); + return hc; + } + + @Override + public boolean equals(Object o){ + if (o instanceof IndexInvalidate == false){ + return false; + } + return indexName.equals(((IndexInvalidate)o).indexName); + } + + public static IndexInvalidate readBinaryMessage(DataInput dataInput) throws IOException { + + + String indexName = dataInput.readUTF(); + return new IndexInvalidate(indexName); + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + BinaryMessage msg = new BinaryMessage(indexName.length()+10); + DataOutputStream os = msg.getOs(); + os.writeInt(BinaryMessage.TYPE_INDEX_INVALIDATE); + os.writeUTF(indexName); + + msgList.add(msg); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java new file mode 100644 index 000000000..e68c1edfe --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java @@ -0,0 +1,689 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; +import javax.persistence.RollbackException; + +import com.avaje.ebean.LogLevel; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.TransactionEvent; +import com.avaje.ebeaninternal.server.persist.BatchControl; +import com.avaje.ebeaninternal.server.transaction.TransactionManager.OnQueryOnly; + +/** + * JDBC Connection based transaction. + */ +public class JdbcTransaction implements SpiTransaction { + + private static final Logger logger = Logger.getLogger(JdbcTransaction.class.getName()); + + private static final String illegalStateMessage = "Transaction is Inactive"; + + /** + * The associated TransactionManager. + */ + final protected TransactionManager manager; + + /** + * The transaction id. + */ + final String id; + + /** + * Flag to indicate if this was an explicitly created Transaction. + */ + final boolean explicit; + + /** + * Set to true if the connection has autoCommit=true initially. + */ + final boolean autoCommit; + + /** + * Behaviour for ending query only transactions. + */ + final OnQueryOnly onQueryOnly; + + /** + * The status of the transaction. + */ + boolean active; + + /** + * The underlying Connection. + */ + Connection connection; + + /** + * Used to queue up persist requests for batch execution. + */ + BatchControl batchControl; + + /** + * The event which holds persisted beans. + */ + TransactionEvent event; + + /** + * Holder of the objects fetched to ensure unique objects are used. + */ + PersistenceContext persistenceContext; + + /** + * Used to give developers more control over the insert update and delete + * functionality. + */ + boolean persistCascade = true; + + /** + * Flag used for performance to skip commit or rollback of query only + * transactions in read committed transaction isolation. + */ + boolean queryOnly = true; + + boolean localReadOnly; + + LogLevel logLevel; + + /** + * Set to true if using batch processing. + */ + boolean batchMode; + + int batchSize = -1; + + boolean batchFlushOnQuery = true; + + Boolean batchGetGeneratedKeys; + + Boolean batchFlushOnMixed; + + /** + * The depth used by batch processing to help the ordering of statements. + */ + int depth = 0; + + HashSet persistingBeans = new HashSet(); + HashSet deletingBeansHash; + + TransactionLogBuffer logBuffer; + + HashMap> derivedRelMap; + + private final Map userObjects = new ConcurrentHashMap(); + + /** + * Create a new JdbcTransaction. + */ + public JdbcTransaction(String id, boolean explicit, LogLevel logLevel, Connection connection, TransactionManager manager) { + try { + this.active = true; + this.id = id; + this.explicit = explicit; + this.logLevel = logLevel; + this.manager = manager; + this.connection = connection; + this.autoCommit = connection.getAutoCommit(); + if (this.autoCommit) { + connection.setAutoCommit(false); + } + this.onQueryOnly = manager == null ? OnQueryOnly.ROLLBACK : manager.getOnQueryOnly(); + this.persistenceContext = new DefaultPersistenceContext(); + + this.logBuffer = new TransactionLogBuffer(50, id); + + } catch (Exception e) { + throw new PersistenceException(e); + } + } + + public String toString() { + return "Trans[" + id + "]"; + } + + public List getDerivedRelationship(Object bean) { + if (derivedRelMap == null) { + return null; + } + Integer key = Integer.valueOf(System.identityHashCode(bean)); + return derivedRelMap.get(key); + } + + public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { + if (derivedRelMap == null) { + derivedRelMap = new HashMap>(); + } + Integer key = Integer.valueOf(System.identityHashCode(derivedRelationship.getAssocBean())); + + List list = derivedRelMap.get(key); + if (list == null) { + list = new ArrayList(); + derivedRelMap.put(key, list); + } + list.add(derivedRelationship); + } + + /** + * Add a bean to the registed list. + *

    + * This is to handle bi-directional relationships where both sides Cascade. + *

    + */ + public void registerDeleteBean(Integer persistingBean) { + if (deletingBeansHash == null) { + deletingBeansHash = new HashSet(); + } + deletingBeansHash.add(persistingBean); + } + + /** + * Unregister the persisted bean. + */ + public void unregisterDeleteBean(Integer persistedBean) { + if (deletingBeansHash != null) { + deletingBeansHash.remove(persistedBean); + } + } + + /** + * Return true if this is a bean that has already been saved/deleted. + */ + public boolean isRegisteredDeleteBean(Integer persistingBean) { + if (deletingBeansHash == null) { + return false; + } else { + return deletingBeansHash.contains(persistingBean); + } + } + + /** + * Unregister the persisted bean. + */ + public void unregisterBean(Object bean) { + persistingBeans.remove(bean); + } + + /** + * Return true if this is a bean that has already been saved. This will + * register the bean if it is not already. + */ + public boolean isRegisteredBean(Object bean) { + return !persistingBeans.add(bean); + } + + /** + * Return the depth of the current persist request plus the diff. This has the + * effect of changing the current depth and returning the new value. Pass + * diff=0 to return the current depth. + *

    + * The depth of 0 is for the initial persist request. It is modified as the + * cascading of the save or delete traverses to the the associated Ones (-1) + * and associated Manys (+1). + *

    + *

    + * The depth is used to help the ordering of batched statements. + *

    + * + * @param diff + * the amount to add or subtract from the depth. + * @return the current depth plus the diff + */ + public int depth(int diff) { + depth += diff; + return depth; + } + + public boolean isReadOnly() { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + try { + return connection.isReadOnly(); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + public void setReadOnly(boolean readOnly) { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + try { + localReadOnly = readOnly; + connection.setReadOnly(readOnly); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + public void setBatchMode(boolean batchMode) { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + this.batchMode = batchMode; + } + + public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) { + this.batchGetGeneratedKeys = getGeneratedKeys; + if (batchControl != null) { + batchControl.setGetGeneratedKeys(getGeneratedKeys); + } + } + + public void setBatchFlushOnMixed(boolean batchFlushOnMixed) { + this.batchFlushOnMixed = batchFlushOnMixed; + if (batchControl != null) { + batchControl.setBatchFlushOnMixed(batchFlushOnMixed); + } + } + + /** + * Return the batchSize specifically set for this transaction or 0. + *

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

    + */ + public int getBatchSize() { + return batchSize; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + if (batchControl != null) { + batchControl.setBatchSize(batchSize); + } + } + + public boolean isBatchFlushOnQuery() { + return batchFlushOnQuery; + } + + public void setBatchFlushOnQuery(boolean batchFlushOnQuery) { + this.batchFlushOnQuery = batchFlushOnQuery; + } + + /** + * Return true if this request should be batched. Returning false means that + * this request should be executed immediately. + */ + public boolean isBatchThisRequest() { + if (!explicit && depth <= 0) { + // implicit transaction ... no gain + // by batching where depth <= 0 + return false; + } + return batchMode; + } + + public BatchControl getBatchControl() { + return batchControl; + } + + /** + * Set the BatchControl to the transaction. This is done once per transaction + * on the first persist request. + */ + public void setBatchControl(BatchControl batchControl) { + queryOnly = false; + this.batchControl = batchControl; + // in case these parameters have already been set + if (batchGetGeneratedKeys != null) { + batchControl.setGetGeneratedKeys(batchGetGeneratedKeys); + } + if (batchSize != -1) { + batchControl.setBatchSize(batchSize); + } + if (batchFlushOnMixed != null) { + batchControl.setBatchFlushOnMixed(batchFlushOnMixed); + } + } + + /** + * Flush any queued persist requests. + *

    + * This is general will result in a number of batched PreparedStatements + * executing. + *

    + */ + public void flushBatch() { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + if (batchControl != null) { + batchControl.flush(); + } + } + + public void batchFlush() { + flushBatch(); + } + + /** + * Return the persistence context associated with this transaction. + */ + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + /** + * Set the persistence context to this transaction. + *

    + * This could be considered similar to EJB3 Extended PersistanceContext. In + * that you get the PersistanceContext from a transaction, hold onto it, and + * then set it back later to a second transaction. + *

    + */ + public void setPersistenceContext(PersistenceContext context) { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + this.persistenceContext = context; + } + + /** + * Return the underlying TransactionEvent. + */ + public TransactionEvent getEvent() { + queryOnly = false; + if (event == null) { + event = new TransactionEvent(); + } + return event; + } + + /** + * Set whether transaction logging is on for this transaction. + */ + public void setLoggingOn(boolean loggingOn) { + if (loggingOn) { + logLevel = LogLevel.SQL; + } else { + logLevel = LogLevel.NONE; + } + } + + /** + * Return true if this was an explicitly created transaction. + */ + public boolean isExplicit() { + return explicit; + } + + public boolean isLogSql() { + return logLevel.ordinal() >= LogLevel.SQL.ordinal(); + } + + public boolean isLogSummary() { + return logLevel.ordinal() >= LogLevel.SUMMARY.ordinal(); + } + + public LogLevel getLogLevel() { + return logLevel; + } + + public void setLogLevel(LogLevel logLevel) { + this.logLevel = logLevel; + } + + /** + * Log a message to the transaction log - for PUBLIC use. + */ + public void log(String msg) { + if (isLogSummary()) { + logInternal(msg); + } + } + + /** + * Log a message to the transaction log - for Ebean INTERNAL use. The LogLevel + * should be explicitly checked before calling this method. + */ + public void logInternal(String msg) { + if (manager != null) { + if (logBuffer.add(msg)) { + // buffer full so flush it + manager.log(logBuffer); + logBuffer = logBuffer.newBuffer(); + } + } + } + + /** + * Return the transaction id. + */ + public String getId() { + return id; + } + + /** + * Return the underlying connection for internal use. + */ + public Connection getInternalConnection() { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + return connection; + } + + /** + * Return the underlying connection for public use. + */ + public Connection getConnection() { + queryOnly = false; + return getInternalConnection(); + } + + protected void deactivate() { + try { + if (localReadOnly) { + // reset readOnly status prior to returning to pool + connection.setReadOnly(false); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, "Error setting to readOnly?", e); + } + try { + if (this.autoCommit) { + // reset the autoCommit status prior to returning to pool + connection.setAutoCommit(true); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, "Error setting to readOnly?", e); + } + try { + connection.close(); + } catch (Exception ex) { + // the connection pool will automatically remove the + // connection if it does not pass the test + logger.log(Level.SEVERE, "Error closing connection", ex); + } + connection = null; + active = false; + } + + public TransactionLogBuffer getLogBuffer() { + return logBuffer; + } + + /** + * Notify the transaction manager. + */ + protected void notifyCommit() { + if (manager == null) { + return; + } + if (queryOnly) { + manager.notifyOfQueryOnly(true, this, null); + } else { + manager.notifyOfCommit(this); + } + } + + /** + * Rollback, Commit or Close for query only transaction. + *

    + * For a transaction that was used for queries only we can choose to either + * rollback or just close the connection for performance. + *

    + */ + private void commitQueryOnly() { + try { + switch (onQueryOnly) { + case ROLLBACK: + connection.rollback(); + break; + case COMMIT: + connection.commit(); + break; + case CLOSE_ON_READCOMMITTED: + // Connection is closed via deactivate() which follows + // This optimisation is only available at READ COMMITTED Isolation + break; + default: + connection.rollback(); + } + } catch (SQLException e) { + String m = "Error when ending a query only transaction via " + onQueryOnly; + logger.log(Level.SEVERE, m, e); + } + } + + /** + * Commit the transaction. + */ + public void commit() throws RollbackException { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + try { + if (queryOnly) { + // can rollback or just close for performance + commitQueryOnly(); + } else { + // commit + if (batchControl != null && !batchControl.isEmpty()) { + batchControl.flush(); + } + connection.commit(); + } + // these will not throw an exception + deactivate(); + notifyCommit(); + + } catch (Exception e) { + throw new RollbackException(e); + } + } + + /** + * Notify the transaction manager. + */ + protected void notifyRollback(Throwable cause) { + if (manager == null) { + return; + } + if (queryOnly) { + manager.notifyOfQueryOnly(false, this, cause); + } else { + manager.notifyOfRollback(this, cause); + } + } + + /** + * Rollback the transaction. + */ + public void rollback() throws PersistenceException { + rollback(null); + } + + /** + * Rollback the transaction. If there is a throwable it is logged as the cause + * in the transaction log. + */ + public void rollback(Throwable cause) throws PersistenceException { + if (!isActive()) { + throw new IllegalStateException(illegalStateMessage); + } + try { + connection.rollback(); + + // these will not throw an exception + deactivate(); + notifyRollback(cause); + + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + /** + * If the transaction is active then perform rollback. + */ + public void end() throws PersistenceException { + if (isActive()) { + rollback(); + } + } + + /** + * Return true if the transaction is active. + */ + public boolean isActive() { + return active; + } + + public boolean isPersistCascade() { + return persistCascade; + } + + public void setPersistCascade(boolean persistCascade) { + this.persistCascade = persistCascade; + } + + public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) { + getEvent().add(tableName, inserts, updates, deletes); + } + + public void putUserObject(String name, Object value) { + userObjects.put(name, value); + } + + public Object getUserObject(String name) { + return userObjects.get(name); + } + + public final TransactionManager getTransactionManger() { + return manager; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransaction.java new file mode 100644 index 000000000..e1d42f2ac --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransaction.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.sql.SQLException; + +import javax.persistence.PersistenceException; +import javax.sql.DataSource; +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import com.avaje.ebean.LogLevel; + +/** + * Jta based transaction. + */ +public class JtaTransaction extends JdbcTransaction { + + private UserTransaction userTransaction; + + private DataSource dataSource; + + private boolean commmitted = false; + + private boolean newTransaction = false; + + + /** + * Create the JtaTransaction. + */ + public JtaTransaction(String id, boolean explicit, LogLevel logLevel, UserTransaction utx, DataSource ds, TransactionManager manager) { + super(id, explicit, logLevel, null, manager); + userTransaction = utx; + dataSource = ds; + + try { + newTransaction = userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION; + if (newTransaction) { + userTransaction.begin(); + } + } catch (Exception e) { + throw new PersistenceException(e); + } + + try { + // Open JDBC Connection + this.connection = dataSource.getConnection(); + if (connection == null) { + throw new PersistenceException("The DataSource returned a null connection."); + } + if (connection.getAutoCommit()) { + connection.setAutoCommit(false); + } + + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + /** + * Commit the transaction. + */ + public void commit() { + if (commmitted) { + throw new PersistenceException("This transaction has already been committed."); + } + try { + try { + if (newTransaction) { + userTransaction.commit(); + } + notifyCommit(); + } finally { + close(); + } + } catch (Exception e) { + throw new PersistenceException(e); + } + commmitted = true; + } + + public void rollback() { + rollback(null); + } + + /** + * Rollback the transaction. + */ + public void rollback(Throwable e) { + if (!commmitted) { + try { + try { + if (userTransaction != null) { + if (newTransaction) { + userTransaction.rollback(); + } else { + userTransaction.setRollbackOnly(); + } + } + notifyRollback(e); + } finally { + close(); + } + } catch (Exception ex) { + throw new PersistenceException(ex); + } + } + + } + + /** + * Close the underlying connection. + */ + private void close() throws SQLException { + if (connection != null) { + connection.close(); + connection = null; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransactionManager.java new file mode 100644 index 000000000..00e6717bb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JtaTransactionManager.java @@ -0,0 +1,249 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.persistence.PersistenceException; +import javax.sql.DataSource; +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.UserTransaction; + +import com.avaje.ebean.LogLevel; +import com.avaje.ebean.config.ExternalTransactionManager; +import com.avaje.ebeaninternal.api.SpiTransaction; + +/** + * Hook into external JTA transaction manager. + * + * @author rbygrave + */ +public class JtaTransactionManager implements ExternalTransactionManager { + + private final static Logger logger = Logger.getLogger(JtaTransactionManager.class.getName()); + + private static final String EBEAN_TXN_RESOURCE = "EBEAN_TXN_RESOURCE"; + + /** + * The data source. + */ + private DataSource dataSource; + + /** + * The Ebean transaction manager. + */ + private TransactionManager transactionManager; + + /** + * The EbeanServer name. + */ + private String serverName; + + /** + * Instantiates a new spring aware transaction scope manager. + */ + public JtaTransactionManager() { + } + + /** + * Initialise this with the Ebean internal transaction manager. + */ + public void setTransactionManager(Object txnMgr) { + + // RB: At this stage not exposing TransactionManager to + // the public API and hence the Object type and casting here + + this.transactionManager = (TransactionManager) txnMgr; + this.dataSource = transactionManager.getDataSource(); + this.serverName = transactionManager.getServerName(); + } + + private TransactionSynchronizationRegistry getSyncRegistry() { + try { + InitialContext ctx = new InitialContext(); + return (TransactionSynchronizationRegistry)ctx.lookup("java:comp/TransactionSynchronizationRegistry"); + } catch (NamingException e){ + throw new PersistenceException(e); + } + } + + private UserTransaction getUserTransaction() { + try { + InitialContext ctx = new InitialContext(); + return (UserTransaction) ctx.lookup("java:comp/UserTransaction"); + } catch (NamingException e){ + // assuming CMT + return new DummyUserTransaction(); + } + } + + /** + * Looks for a current Spring managed transaction and wraps/returns that as a Ebean transaction. + *

    + * Returns null if there is no current spring transaction (lazy loading outside a spring txn etc). + *

    + */ + public Object getCurrentTransaction() { + + TransactionSynchronizationRegistry syncRegistry = getSyncRegistry(); + + SpiTransaction t = (SpiTransaction)syncRegistry.getResource(EBEAN_TXN_RESOURCE); + if (t != null){ + // we have already seen this transaction + return t; + } + + // check current Ebean transaction + SpiTransaction currentEbeanTransaction = DefaultTransactionThreadLocal.get(serverName); + if (currentEbeanTransaction != null){ + // NOT expecting this so log WARNING + String msg = "JTA Transaction - no current txn BUT using current Ebean one "+currentEbeanTransaction.getId(); + logger.log(Level.WARNING, msg); + return currentEbeanTransaction; + } + + UserTransaction ut = getUserTransaction(); + if (ut == null){ + // no current JTA transaction + if (logger.isLoggable(Level.FINE)){ + logger.fine("JTA Transaction - no current txn"); + } + return null; + } + + // This is a transaction that Ebean has not seen before. + + // "wrap" it in a Ebean specific JtaTransaction + String txnId = String.valueOf(System.currentTimeMillis()); + JtaTransaction newTrans = new JtaTransaction(txnId, true, LogLevel.NONE, ut, dataSource, transactionManager); + + // create and register transaction listener + JtaTxnListener txnListener = createJtaTxnListener(newTrans); + + syncRegistry.putResource(EBEAN_TXN_RESOURCE, newTrans); + syncRegistry.registerInterposedSynchronization(txnListener); + + // also put in Ebean ThreadLocal + DefaultTransactionThreadLocal.set(serverName, newTrans); + return newTrans; + } + + + /** + * Create a listener to register with JTA to enable Ebean to be + * notified when transactions commit and rollback. + *

    + * This is used by Ebean to notify it's appropriate listeners and maintain it's server + * cache etc. + *

    + */ + private JtaTxnListener createJtaTxnListener(SpiTransaction t) { + return new JtaTxnListener(transactionManager, t); + } + + private static class DummyUserTransaction implements UserTransaction { + + public void begin() throws NotSupportedException, SystemException { + } + + public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, + SecurityException, IllegalStateException, SystemException { + } + + public int getStatus() throws SystemException { + return 0; + } + + public void rollback() throws IllegalStateException, SecurityException, SystemException { + } + + public void setRollbackOnly() throws IllegalStateException, SystemException { + } + + public void setTransactionTimeout(int seconds) throws SystemException { + } + } + + /** + * A JTA Transaction Synchronization that we register to get notified when a + * managed transaction has been committed or rolled back. + *

    + * When Ebean is notified (of the commit/rollback) it can then manage its + * cache, notify BeanPersistListeners etc. + *

    + */ + private static class JtaTxnListener implements Synchronization { + + private final TransactionManager transactionManager; + + private final SpiTransaction transaction; + + private final String serverName; + + private JtaTxnListener(TransactionManager transactionManager, SpiTransaction t){ + this.transactionManager = transactionManager; + this.transaction = t; + this.serverName = transactionManager.getServerName(); + } + + public void beforeCompletion() { + // Future note: for JPA2 locking we will + // have beforeCommit events to fire + } + + public void afterCompletion(int status) { + + switch (status) { + case Status.STATUS_COMMITTED: + if (logger.isLoggable(Level.FINE)){ + logger.fine("Jta Txn ["+transaction.getId()+"] committed"); + } + transactionManager.notifyOfCommit(transaction); + // Remove this transaction object as it is completed + DefaultTransactionThreadLocal.replace(serverName, null); + break; + + case Status.STATUS_ROLLEDBACK: + if (logger.isLoggable(Level.FINE)){ + logger.fine("Jta Txn ["+transaction.getId()+"] rollback"); + } + transactionManager.notifyOfRollback(transaction, null); + // Remove this transaction object as it is completed + DefaultTransactionThreadLocal.replace(serverName, null); + break; + + default: + logger.fine("Jta Txn ["+transaction.getId()+"] status:"+status); + } + + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java new file mode 100644 index 000000000..f2f7b49ff --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java @@ -0,0 +1,202 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.TransactionEvent; +import com.avaje.ebeaninternal.api.TransactionEventBeans; +import com.avaje.ebeaninternal.api.TransactionEventTable; +import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; +import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; + +/** + * Performs post commit processing using a background thread. + *

    + * This includes Cluster notification, and BeanPersistListeners. + *

    + */ +public final class PostCommitProcessing { + + private static final Logger logger = Logger.getLogger(PostCommitProcessing.class.getName()); + + private final ClusterManager clusterManager; + + private final TransactionEvent event; + + private final String serverName; + + private final TransactionManager manager; + + private final List> persistBeanRequests; + + private final BeanPersistIdMap beanPersistIdMap; + +// private final BeanDeltaMap beanDeltaMap; + + private final RemoteTransactionEvent remoteTransactionEvent; + + private final DeleteByIdMap deleteByIdMap; + + /** + * Create for a TransactionManager and event. + */ + public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, SpiTransaction transaction, TransactionEvent event) { + + this.clusterManager = clusterManager; + this.manager = manager; + this.serverName = manager.getServerName(); + this.event = event; + this.deleteByIdMap = event.getDeleteByIdMap(); + this.persistBeanRequests = createPersistBeanRequests(); + + this.beanPersistIdMap = createBeanPersistIdMap(); + //this.beanDeltaMap = new BeanDeltaMap(event.getBeanDeltas()); + + this.remoteTransactionEvent = createRemoteTransactionEvent(); + } + + public void notifyLocalCacheIndex() { + + // notify cache with bulk insert/update/delete statements + processTableEvents(event.getEventTables()); + + // notify cache with bean changes + event.notifyCache(); + } + + /** + * Table events are where SQL or external tools are used. In this case the + * cache is notified based on the table name (rather than bean type). + */ + private void processTableEvents(TransactionEventTable tableEvents) { + + if (tableEvents != null && !tableEvents.isEmpty()) { + // notify cache with table based changes + BeanDescriptorManager dm = manager.getBeanDescriptorManager(); + for (TableIUD tableIUD : tableEvents.values()) { + dm.cacheNotify(tableIUD); + } + } + } + + public void notifyCluster() { + if (remoteTransactionEvent != null && !remoteTransactionEvent.isEmpty()) { + // send the interesting events to the cluster + if (manager.getClusterDebugLevel() > 0 || logger.isLoggable(Level.FINE)) { + logger.info("Cluster Send: " + remoteTransactionEvent.toString()); + } + + clusterManager.broadcast(remoteTransactionEvent); + } + } + + public Runnable notifyPersistListeners() { + return new Runnable() { + public void run() { + localPersistListenersNotify(); + } + }; + } + + private void localPersistListenersNotify() { + if (persistBeanRequests != null) { + for (int i = 0; i < persistBeanRequests.size(); i++) { + persistBeanRequests.get(i).notifyLocalPersistListener(); + } + } + TransactionEventTable eventTables = event.getEventTables(); + if (eventTables != null && !eventTables.isEmpty()) { + BulkEventListenerMap map = manager.getBulkEventListenerMap(); + for (TableIUD tableIUD : eventTables.values()) { + map.process(tableIUD); + } + } + } + + private List> createPersistBeanRequests() { + TransactionEventBeans eventBeans = event.getEventBeans(); + if (eventBeans != null) { + return eventBeans.getRequests(); + } + return null; + } + + private BeanPersistIdMap createBeanPersistIdMap() { + + if (persistBeanRequests == null) { + return null; + } + + BeanPersistIdMap m = new BeanPersistIdMap(); + for (int i = 0; i < persistBeanRequests.size(); i++) { + persistBeanRequests.get(i).addToPersistMap(m); + } + return m; + } + + private RemoteTransactionEvent createRemoteTransactionEvent() { + + if (!clusterManager.isClustering()) { + return null; + } + + RemoteTransactionEvent remoteTransactionEvent = new RemoteTransactionEvent(serverName); + +// if (beanDeltaMap != null) { +// for (BeanDeltaList deltaList : beanDeltaMap.deltaLists()) { +// remoteTransactionEvent.addBeanDeltaList(deltaList); +// } +// } + + if (beanPersistIdMap != null) { + for (BeanPersistIds beanPersist : beanPersistIdMap.values()) { + remoteTransactionEvent.addBeanPersistIds(beanPersist); + } + } + + if (deleteByIdMap != null) { + remoteTransactionEvent.setDeleteByIdMap(deleteByIdMap); + } + + TransactionEventTable eventTables = event.getEventTables(); + if (eventTables != null && !eventTables.isEmpty()) { + for (TableIUD tableIUD : eventTables.values()) { + remoteTransactionEvent.addTableIUD(tableIUD); + } + } + + Set indexInvalidations = event.getIndexInvalidations(); + if (indexInvalidations != null) { + for (IndexInvalidate indexInvalidate : indexInvalidations) { + remoteTransactionEvent.addIndexInvalidate(indexInvalidate); + } + } + + return remoteTransactionEvent; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/RemoteTransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/RemoteTransactionEvent.java new file mode 100644 index 000000000..81e1ace8f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/RemoteTransactionEvent.java @@ -0,0 +1,200 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; +import com.avaje.ebeaninternal.server.cluster.BinaryMessageList; + +public class RemoteTransactionEvent implements Runnable { + + private List beanPersistList = new ArrayList(); + + private List tableList; + + private List beanDeltaLists; + + private BeanDeltaMap beanDeltaMap; + + private List indexEventList; + + private Set indexInvalidations; + + private DeleteByIdMap deleteByIdMap; + + private String serverName; + + private transient SpiEbeanServer server; + + public RemoteTransactionEvent(String serverName) { + this.serverName = serverName; + } + + public RemoteTransactionEvent(SpiEbeanServer server) { + this.server = server; + } + + public void run() { + server.remoteTransactionEvent(this); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + if (beanDeltaMap != null){ + sb.append(beanDeltaMap); + } + sb.append(beanPersistList); + if (tableList != null){ + sb.append(tableList); + } + return sb.toString(); + } + + public void writeBinaryMessage(BinaryMessageList msgList) throws IOException { + + if (indexInvalidations != null){ + for (IndexInvalidate indexInvalidate : indexInvalidations) { + indexInvalidate.writeBinaryMessage(msgList); + } + } + + if (tableList != null){ + for (int i = 0; i < tableList.size(); i++) { + tableList.get(i).writeBinaryMessage(msgList); + } + } + + if (deleteByIdMap != null){ + for (BeanPersistIds deleteIds : deleteByIdMap.values()) { + deleteIds.writeBinaryMessage(msgList); + } + } + + if (beanPersistList != null){ + for (int i = 0; i < beanPersistList.size(); i++) { + beanPersistList.get(i).writeBinaryMessage(msgList); + } + } + + if (beanDeltaLists != null){ + for (int i = 0; i < beanDeltaLists.size(); i++) { + beanDeltaLists.get(i).writeBinaryMessage(msgList); + } + } + + if (indexEventList != null){ + for (int i = 0; i < indexEventList.size(); i++) { + indexEventList.get(i).writeBinaryMessage(msgList); + } + } + } + + public boolean isEmpty() { + return beanPersistList.isEmpty() && (tableList == null || tableList.isEmpty()); + } + + public void addBeanPersistIds(BeanPersistIds beanPersist){ + beanPersistList.add(beanPersist); + } + + public void addIndexInvalidate(IndexInvalidate indexInvalidate){ + if (indexInvalidations == null){ + indexInvalidations = new HashSet(); + } + indexInvalidations.add(indexInvalidate); + } + + public void addTableIUD(TableIUD tableIud){ + if (tableList == null){ + tableList = new ArrayList(4); + } + tableList.add(tableIud); + } + + public void addBeanDeltaList(BeanDeltaList deltaList){ + if (beanDeltaLists == null){ + beanDeltaLists = new ArrayList(); + } + beanDeltaLists.add(deltaList); + } + + public void addBeanDelta(BeanDelta beanDelta){ + if (beanDeltaMap == null){ + beanDeltaMap = new BeanDeltaMap(); + } + beanDeltaMap.addBeanDelta(beanDelta); + } + + public void addIndexEvent(IndexEvent indexEvent){ + if (indexEventList == null){ + indexEventList = new ArrayList(2); + } + indexEventList.add(indexEvent); + } + + public String getServerName() { + return serverName; + } + + public SpiEbeanServer getServer() { + return server; + } + + public void setServer(SpiEbeanServer server) { + this.server = server; + } + + public DeleteByIdMap getDeleteByIdMap() { + return deleteByIdMap; + } + + public void setDeleteByIdMap(DeleteByIdMap deleteByIdMap) { + this.deleteByIdMap = deleteByIdMap; + } + + public Set getIndexInvalidations() { + return indexInvalidations; + } + + public List getIndexEventList() { + return indexEventList; + } + + public List getTableIUDList() { + return tableList; + } + + public List getBeanPersistList() { + return beanPersistList; + } + + public List getBeanDeltaLists() { + if (beanDeltaMap != null){ + beanDeltaLists.addAll(beanDeltaMap.deltaLists()); + } + return beanDeltaLists; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogBuffer.java new file mode 100644 index 000000000..fd158dc0f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogBuffer.java @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.util.ArrayList; +import java.util.List; + +/** + * Buffer of transaction messages. + *

    + * For performance reasons we add all the transaction messages to an instance of + * TransactionLogBuffer and then when the buffer is full or the transaction ends + * we send the buffer to the transaction manager to log. + *

    + * + * @author rbygrave + * + */ +public class TransactionLogBuffer { + + private final String transactionId; + + private final ArrayList buffer; + + private final int maxSize; + + private int currentSize; + + /** + * Create the buffer with a maxSize and transaction id. + */ + public TransactionLogBuffer(int maxSize, String transactionId) { + this.maxSize = maxSize; + this.transactionId = transactionId; + this.buffer = new ArrayList(maxSize); + } + + /** + * Create new buffer using the same configuration. + */ + public TransactionLogBuffer newBuffer() { + return new TransactionLogBuffer(maxSize, transactionId); + } + + /** + * Return the transaction id. + */ + public String getTransactionId() { + return transactionId; + } + + /** + * Add a message to the buffer. + */ + public boolean add(String msg) { + buffer.add(new LogEntry(msg)); + return ++currentSize >= maxSize; + } + + /** + * Return true if the buffer is empty. + */ + public boolean isEmpty() { + return buffer.isEmpty(); + } + + /** + * Return all the messages. + */ + public List messages() { + return buffer; + } + + /** + * Entry in the buffer. + */ + public class LogEntry { + + private final long timestamp; + private final String msg; + + /** + * Construct an entry. + */ + public LogEntry(String msg) { + this.timestamp = System.currentTimeMillis(); + this.msg = msg; + } + + /** + * Return the time the entry was put into the buffer. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Return the text of the message. + */ + public String getMsg() { + return msg; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogManager.java new file mode 100644 index 000000000..a9d423f7d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogManager.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.transaction.log.FileTransactionLoggerWrapper; +import com.avaje.ebeaninternal.server.transaction.log.JuliTransactionLogger; + +/** + * Manages the transaction logs. + */ +public class TransactionLogManager { + + private final TransactionLogWriter logWriter; + + /** + * Create the TransactionLogger. + *

    + * DevNote: This registers a shutdown hook to flush and close the + * sharedLogger. Alternate option would be to flush() the log after each + * write to the log. + *

    + */ + public TransactionLogManager(ServerConfig serverConfig) { + + if (serverConfig.isLoggingToJavaLogger()){ + logWriter = new JuliTransactionLogger(); + } else { + logWriter = new FileTransactionLoggerWrapper(serverConfig); + } + } + + public void shutdown() { + logWriter.shutdown(); + } + + public void log(TransactionLogBuffer logBuffer) { + logWriter.log(logBuffer); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogWriter.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogWriter.java new file mode 100644 index 000000000..7690aaacf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionLogWriter.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +/** + * Write transaction log events to a file or other destination. + */ +public interface TransactionLogWriter { + + /** + * Log all the messages in the buffer. + */ + public void log(TransactionLogBuffer logBuffer); + + /** + * Shutdown the writer. + */ + public void shutdown(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java new file mode 100644 index 000000000..d667e5443 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java @@ -0,0 +1,560 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.LogLevel; +import com.avaje.ebean.TxIsolation; +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.event.TransactionEventListener; +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.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; + +/** + * Manages transactions. + *

    + * Keeps the Cache, Cluster and Lucene indexes in synch when transactions are + * committed. + *

    + */ +public class TransactionManager { + + private static final Logger logger = Logger.getLogger(TransactionManager.class.getName()); + + /** + * The behaviour desired when ending a query only transaction. + */ + public enum OnQueryOnly { + + /** + * Rollback the transaction. + */ + ROLLBACK, + + /** + * Just close the transaction. + */ + CLOSE_ON_READCOMMITTED, + + /** + * Commit the transaction + */ + COMMIT + } + + private final BeanDescriptorManager beanDescriptorManager; + + private LogLevel logLevel; + + /** + * The logger. + */ + private final TransactionLogManager transLogger; + + /** + * Prefix for transaction id's (logging). + */ + private final String prefix; + + private final String externalTransPrefix; + + /** + * The dataSource of connections. + */ + private final DataSource dataSource; + + /** + * Flag to indicate the default Isolation is READ COMMITTED. This enables us + * to close queryOnly transactions rather than commit or rollback them. + */ + private final OnQueryOnly onQueryOnly; + + /** + * The default batchMode for transactions. + */ + private final boolean defaultBatchMode; + + private final BackgroundExecutor backgroundExecutor; + + private final ClusterManager clusterManager; + + private final int commitDebugLevel; + + private final String serverName; + + /** + * Id's for transaction logging. + */ + private AtomicLong transactionCounter = new AtomicLong(1000); + + private int clusterDebugLevel; + + private final BulkEventListenerMap bulkEventListenerMap; + + private TransactionEventListener[] transactionEventListeners; + + /** + * Create the TransactionManager + */ + public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config, + BeanDescriptorManager descMgr, BootupClasses bootupClasses) { + + this.beanDescriptorManager = descMgr; + this.clusterManager = clusterManager; + this.serverName = config.getName(); + + this.logLevel = config.getLoggingLevel(); + this.transLogger = new TransactionLogManager(config); + this.backgroundExecutor = backgroundExecutor; + this.dataSource = config.getDataSource(); + this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners()); + + List transactionEventListeners = bootupClasses.getTransactionEventListeners(); + this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]); + + // log some transaction events using a java util logger + this.commitDebugLevel = GlobalProperties.getInt("ebean.commit.debuglevel", 0); + this.clusterDebugLevel = GlobalProperties.getInt("ebean.cluster.debuglevel", 0); + + this.defaultBatchMode = config.isPersistBatching(); + + this.prefix = GlobalProperties.get("transaction.prefix", ""); + this.externalTransPrefix = GlobalProperties.get("transaction.prefix", "e"); + + String value = GlobalProperties.get("transaction.onqueryonly", "ROLLBACK").toUpperCase().trim(); + this.onQueryOnly = getOnQueryOnly(value, dataSource); + } + + public void shutdown() { + transLogger.shutdown(); + } + + public BeanDescriptorManager getBeanDescriptorManager() { + return beanDescriptorManager; + } + + public BulkEventListenerMap getBulkEventListenerMap() { + return bulkEventListenerMap; + } + + /** + * Return the logging level for transactions. + */ + public LogLevel getTransactionLogLevel(){ + return logLevel; + } + + /** + * Set the log level for transactions. + */ + public void setTransactionLogLevel(LogLevel logLevel){ + this.logLevel = logLevel; + } + + /** + * Return the behaviour to use when a query only transaction is committed. + *

    + * There is a potential optimisation available when read committed is the default + * isolation level. If it is, then Connections used only for queries do not require + * commit or rollback but instead can just be put back into the pool via close(). + *

    + *

    + * If the Isolation level is higher (say SERIALIZABLE) then Connections used + * just for queries do need to be committed or rollback after the query. + *

    + */ + private OnQueryOnly getOnQueryOnly(String onQueryOnly, DataSource ds) { + + + if (onQueryOnly.equals("COMMIT")){ + return OnQueryOnly.COMMIT; + } + if (onQueryOnly.startsWith("CLOSE")){ + if (!isReadCommitedIsolation(ds)){ + String m = "transaction.queryonlyclose is true but the transaction Isolation Level is not READ_COMMITTED"; + throw new PersistenceException(m); + } else { + return OnQueryOnly.CLOSE_ON_READCOMMITTED; + } + } + // default to rollback + return OnQueryOnly.ROLLBACK; + } + + /** + * Return true if the isolation level is read committed. + */ + private boolean isReadCommitedIsolation(DataSource ds) { + + Connection c = null; + try { + c = ds.getConnection(); + + int isolationLevel = c.getTransactionIsolation(); + return (isolationLevel == Connection.TRANSACTION_READ_COMMITTED); + + } catch (SQLException ex) { + String m = "Errored trying to determine the default Isolation Level"; + throw new PersistenceException(m, ex); + + } finally { + try { + if (c != null) { + c.close(); + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, "closing connection", ex); + } + } + } + + public String getServerName() { + return serverName; + } + + public DataSource getDataSource() { + return dataSource; + } + + /** + * Return the cluster debug level. + */ + public int getClusterDebugLevel() { + return clusterDebugLevel; + } + + /** + * Set the cluster debug level. + */ + public void setClusterDebugLevel(int clusterDebugLevel) { + this.clusterDebugLevel = clusterDebugLevel; + } + + /** + * Defines the type of behaviour to use when closing a transaction that was used to query data only. + */ + public OnQueryOnly getOnQueryOnly() { + return onQueryOnly; + } + + /** + * Return the TransactionLogger used by this TransactionManager. + */ + public TransactionLogManager getLogger() { + return transLogger; + } + + public void log(TransactionLogBuffer logBuffer){ + if (!logBuffer.isEmpty()){ + transLogger.log(logBuffer); + } + } + + /** + * Wrap the externally supplied Connection. + */ + public SpiTransaction wrapExternalConnection(Connection c) { + + return wrapExternalConnection(externalTransPrefix + c.hashCode(), c); + } + + /** + * Wrap an externally supplied Connection with a known transaction id. + */ + public SpiTransaction wrapExternalConnection(String id, Connection c) { + + ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, logLevel, c, this); + + // set the default batch mode. This can be on for + // jdbc drivers that support getGeneratedKeys + if (defaultBatchMode){ + t.setBatchMode(true); + } + + return t; + } + + /** + * Create a new Transaction. + */ + public SpiTransaction createTransaction(boolean explicit, int isolationLevel) { + Connection c = null; + try { + c = dataSource.getConnection(); + long id = transactionCounter.incrementAndGet(); + + JdbcTransaction t = new JdbcTransaction(prefix + id, explicit, logLevel, c, this); + + // set the default batch mode. This can be on for + // jdbc drivers that support getGeneratedKeys + if (defaultBatchMode){ + t.setBatchMode(true); + } + if (isolationLevel > -1) { + c.setTransactionIsolation(isolationLevel); + } + + if (commitDebugLevel >= 3){ + String msg = "Transaction ["+t.getId()+"] begin"; + if (isolationLevel > -1){ + TxIsolation txi = TxIsolation.fromLevel(isolationLevel); + msg += " isolationLevel["+txi+"]"; + } + logger.info(msg); + } + + return t; + + } catch (SQLException ex) { + // close connection on failed creation + try { + if (c != null){ + c.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE,"Error closing failed connection", e); + } + throw new PersistenceException(ex); + } + } + + public SpiTransaction createQueryTransaction() { + Connection c = null; + try { + c = dataSource.getConnection(); + long id = transactionCounter.incrementAndGet(); + + JdbcTransaction t = new JdbcTransaction(prefix + id, false, logLevel, c, this); + + // set the default batch mode. Can be true for + // jdbc drivers that support getGeneratedKeys + if (defaultBatchMode){ + t.setBatchMode(true); + } + + if (commitDebugLevel >= 3){ + logger.info("Transaction ["+t.getId()+"] begin - queryOnly"); + } + + return t; + + } catch (PersistenceException ex) { + // close the connection and re-throw the exception + try { + if (c != null) { + c.close(); + } + } catch (SQLException e) { + logger.log(Level.SEVERE,"Error closing failed connection", e); + } + throw ex; + + } catch (SQLException ex) { + // don't need to close connection in this case + throw new PersistenceException(ex); + } + } + + /** + * Process a local rolled back transaction. + */ + public void notifyOfRollback(SpiTransaction transaction, Throwable cause) { + + try { + for (TransactionEventListener listener : transactionEventListeners) { + listener.postTransactionRollback(transaction, cause); + } + + if (transaction.isLogSummary() || commitDebugLevel >= 1) { + String msg = "Rollback"; + if (cause != null){ + msg += " error: "+formatThrowable(cause); + } + if (transaction.isLogSummary()) { + transaction.logInternal(msg); + } + + if (commitDebugLevel >= 1){ + logger.info("Transaction ["+transaction.getId()+"] "+msg); + } + } + + log(transaction.getLogBuffer()); + } catch (Exception ex) { + String m = "Potentially Transaction Log incomplete due to error:"; + logger.log(Level.SEVERE, m, ex); + } + } + + /** + * Query only transaction in read committed isolation. + */ + public void notifyOfQueryOnly(boolean onCommit, SpiTransaction transaction, Throwable cause) { + + try { + if (commitDebugLevel >= 2){ + String msg; + if (onCommit){ + msg = "Commit queryOnly"; + + } else { + msg = "Rollback queryOnly"; + if (cause != null){ + msg += " error: "+formatThrowable(cause); + } + } + if (transaction.isLogSummary()) { + transaction.logInternal(msg); + } + logger.info("Transaction ["+transaction.getId()+"] "+msg); + } + + log(transaction.getLogBuffer()); + + } catch (Exception ex) { + String m = "Potentially Transaction Log incomplete due to error:"; + logger.log(Level.SEVERE, m, ex); + } + } + + private String formatThrowable(Throwable e){ + if (e == null){ + return ""; + } + StringBuilder sb = new StringBuilder(); + formatThrowable(e, sb); + return sb.toString(); + } + + private void formatThrowable(Throwable e, StringBuilder sb){ + + sb.append(e.toString()); + StackTraceElement[] stackTrace = e.getStackTrace(); + if (stackTrace.length > 0){ + sb.append(" stack0: "); + sb.append(stackTrace[0]); + } + Throwable cause = e.getCause(); + if (cause != null){ + sb.append(" cause: "); + formatThrowable(cause, sb); + } + } + + /** + * Process a local committed transaction. + */ + public void notifyOfCommit(SpiTransaction transaction) { + + try { + + log(transaction.getLogBuffer()); + + PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction, + transaction.getEvent()); + + postCommit.notifyLocalCacheIndex(); + postCommit.notifyCluster(); + + // cluster and text indexing + backgroundExecutor.execute(postCommit.notifyPersistListeners()); + + for (TransactionEventListener listener : transactionEventListeners) { + listener.postTransactionCommit(transaction); + } + + if (commitDebugLevel >= 1) { + logger.info("Transaction ["+transaction.getId()+"] commit"); + } + } catch (Exception ex) { + String m = "NotifyOfCommit failed. Cache/Lucene potentially not notified."; + logger.log(Level.SEVERE, m, ex); + } + } + + + + + /** + * Process a Transaction that comes from another framework or local code. + *

    + * For cases where raw SQL/JDBC or other frameworks are used this can + * invalidate the appropriate parts of the cache. + *

    + */ + public void externalModification(TransactionEventTable tableEvents) { + + TransactionEvent event = new TransactionEvent(); + event.add(tableEvents); + + PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, null, event); + + // invalidate parts of local cache and index + postCommit.notifyLocalCacheIndex(); + + backgroundExecutor.execute(postCommit.notifyPersistListeners()); + } + + + /** + * Notify local BeanPersistListeners etc of events from another server in the cluster. + */ + public void remoteTransactionEvent(RemoteTransactionEvent remoteEvent) { + + if (clusterDebugLevel > 0 || logger.isLoggable(Level.FINE)){ + logger.info("Cluster Received: "+remoteEvent.toString()); + } + + List tableIUDList = remoteEvent.getTableIUDList(); + if (tableIUDList != null){ + for (int i = 0; i < tableIUDList.size(); i++) { + TableIUD tableIUD = tableIUDList.get(i); + beanDescriptorManager.cacheNotify(tableIUD); + } + } + + List beanPersistList = remoteEvent.getBeanPersistList(); + if (beanPersistList != null){ + for (int i = 0; i < beanPersistList.size(); i++) { + BeanPersistIds beanPersist = beanPersistList.get(i); + beanPersist.notifyCacheAndListener(); + } + } + + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionMap.java new file mode 100644 index 000000000..d579f69ec --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionMap.java @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.util.HashMap; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiTransaction; + + +/** + * Current transactions mapped by server name. + */ +public class TransactionMap { + + /** + * Map of State by serverName. + */ + private HashMap map = new HashMap(); + + public String toString() { + return map.toString(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Return the State for a given serverName. + */ + public State getState(String serverName) { + + return map.get(serverName); + } + + /** + * Return the State for a given serverName. + */ + public State getStateWithCreate(String serverName) { + + State state = map.get(serverName); + if (state == null){ + state = new State(); + map.put(serverName, state); + } + return state; + } + + /** + * Remove and return the State for a given serverName. + */ + public State removeState(String serverName) { + return map.remove(serverName); + } + + /** + * The transaction and whether it is active. + */ + public static class State { + + SpiTransaction transaction; + + public String toString() { + return "txn["+transaction+"]"; + } + + public SpiTransaction get() { + return transaction; + } + + /** + * Set the transaction. This will now be the current transaction. + */ + public void set(SpiTransaction trans) { + + if (transaction != null && transaction.isActive()){ + String m = "The existing transaction is still active?"; + throw new PersistenceException(m); + } + transaction = trans; + } + + + /** + * Commit the transaction. + */ + public void commit() { + transaction.commit(); + transaction = null; + } + + /** + * Rollback the transaction. + */ + public void rollback() { + transaction.rollback(); + transaction = null; + } + + /** + * End the transaction. + */ + public void end() { + if (transaction != null){ + transaction.end(); + transaction = null; + } + } + + /** + * Used to replace transaction with a proxy. + */ + public void replace(SpiTransaction trans) { + transaction = trans; + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionScopeManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionScopeManager.java new file mode 100644 index 000000000..bd7fd7531 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionScopeManager.java @@ -0,0 +1,58 @@ +package com.avaje.ebeaninternal.server.transaction; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiTransactionScopeManager; + +/** + * Manages the Transactions typically held in a ThreadLocal. + */ +public abstract class TransactionScopeManager implements SpiTransactionScopeManager { + + protected final TransactionManager transactionManager; + + protected final String serverName; + + public TransactionScopeManager(TransactionManager transactionManager){ + this.transactionManager = transactionManager; + this.serverName = transactionManager.getServerName(); + } + + /** + * Return the current Transaction for this serverName and Thread. + */ + public abstract SpiTransaction get(); + + /** + * Set a new Transaction for this serverName and Thread. + */ + public abstract void set(SpiTransaction trans); + + /** + * Commit the current transaction. + */ + public abstract void commit(); + + /** + * Rollback the current transaction. + */ + public abstract void rollback(); + + + /** + * Rollback if required. + */ + public abstract void end(); + + /** + * Replace the current transaction with this one. + *

    + * Used for Background fetching and Nested transaction scopes. + *

    + *

    + * Used for background fetching. Replaces the current transaction with a + * 'dummy' transaction. The current transaction is given to the background + * thread so it can continue the fetch. + *

    + */ + public abstract void replace(SpiTransaction trans); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/WeakValueMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/WeakValueMap.java new file mode 100644 index 000000000..723de3632 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/WeakValueMap.java @@ -0,0 +1,152 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +/** + * A Weak value map designed for use with DefaultPersistenceContext. + *

    + * This provides the mechanism where entries in the persistence context will be + * automatically removed when they are not referenced externally. + *

    + * + * @author mario, rbygrave + */ +public class WeakValueMap { + + protected final ReferenceQueue refQueue = new ReferenceQueue(); + + /** + * Backing map. + */ + private final Map> backing; + + /** + * Hold the key with the value for expunge purposes. + */ + private static class WeakReferenceWithKey extends WeakReference { + + private final K key; + + public WeakReferenceWithKey(K key, V referent, ReferenceQueue q) { + super(referent, q); + this.key = key; + } + + public K getKey() { + return key; + } + } + + public WeakValueMap() { + this.backing = new HashMap>(); + } + + private WeakReferenceWithKey createReference(K key, V value) { + return new WeakReferenceWithKey(key, value, refQueue); + } + + @SuppressWarnings({ "rawtypes" }) + private void expunge() { + + Reference ref; + + while ((ref = refQueue.poll()) != null) { + backing.remove(((WeakReferenceWithKey) ref).getKey()); + } + } + + /** + * Put the key value pair if there is not already a matching entry. If there + * is an existing entry then return that instead. + */ + public Object putIfAbsent(K key, V value) { + expunge(); + + Reference ref = backing.get(key); + if (ref != null) { + V existingValue = ref.get(); + if (existingValue != null) { + // it is not absent + return existingValue; + } + } + // put the new value and return null + // indicating the put was successful + backing.put(key, createReference(key, value)); + return null; + } + + public void put(K key, V value) { + expunge(); + + backing.put(key, createReference(key, value)); + } + + public V get(K key) { + expunge(); + + Reference v = backing.get(key); + return v == null ? null : v.get(); + } + + public int size() { + expunge(); + + return backing.size(); + } + + public boolean isEmpty() { + expunge(); + + return backing.isEmpty(); + } + + public boolean containsKey(Object key) { + expunge(); + + return backing.containsKey(key); + } + + public V remove(K key) { + expunge(); + + Reference v = backing.remove(key); + return v == null ? null : v.get(); + } + + public void clear() { + expunge(); + backing.clear(); + expunge(); + } + + public String toString() { + expunge(); + + return backing.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLogger.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLogger.java new file mode 100644 index 000000000..f97129d34 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLogger.java @@ -0,0 +1,401 @@ +/** + * Copyright (C) 2010 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction.log; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.transaction.TransactionLogBuffer; +import com.avaje.ebeaninternal.server.transaction.TransactionLogWriter; +import com.avaje.ebeaninternal.server.transaction.TransactionLogBuffer.LogEntry; + +/** + * Default transaction logger implementation. + *

    + * File based logger that can switch daily. It will include the date in the + * files name. + *

    + *

    + * Administration Note: If log file switching fails it will send the error to + * standard out and standard err print streams. This is assumed to be rather + * unlikely but possible. + *

    + */ +public class FileTransactionLogger implements Runnable, TransactionLogWriter { + + private static final Logger logger = Logger.getLogger(FileTransactionLogger.class.getName()); + + /** + * Used to print stack trace. + */ + private static final String atString = " at "; + + /** + * The newLineChar used instead of NL or CRNL for printing stack traces. + */ + private final String newLinePlaceholder = "\\r\\n"; + + /** + * The maximum number of stack lines to output. This is just for transaction + * logging so 5 is fine. + */ + private final int maxStackTraceLines = 5; + + /** + * Queue that transactions put their LogBuffers onto and this LogBufferWriter + * pulls LogBuffers from. + */ + private final ConcurrentLinkedQueue logBufferQueue = new ConcurrentLinkedQueue(); + + private final Object queueMonitor = new Object(); + + /** + * Thread that is the sole writer of logBuffer entries to the file. + */ + private final Thread logWriterThread; + + private final String threadName; + + /** + * The path of the log file. + */ + private final String filepath; + + /** + * The delimiter to use. + */ + private final String deliminator = ", "; + + /** + * The prefix of the log file name. + */ + private final String logFileName; + + /** + * The file suffix for the logs. + */ + private final String logFileSuffix; + + /** + * Shutdown flag. + */ + private volatile boolean shutdown; + private volatile boolean shutdownComplete; + + /** + * The output stream. + */ + private PrintStream out; + + /** + * The current file path. + */ + private String currentPath; + + /** + * Counter thats incremented when maxBytesPerFile is hit. + */ + private int fileCounter; + + /** + * Roughly the max bytes written to a file before we switch. Switch Daily and + * on hitting max bytes. + */ + private long maxBytesPerFile; + + /** + * Roughly the bytes written to a file. + */ + private long bytesWritten; + + public FileTransactionLogger(String threadName, String dir, String logFileName, int maxBytesPerFile) { + this(threadName, dir, logFileName, "log", maxBytesPerFile); + } + + public FileTransactionLogger(String threadName, String dir, String logFileName, String suffix, int maxBytesPerFile) { + this.threadName = threadName; + this.logFileName = logFileName; + this.logFileSuffix = "." + suffix; + this.maxBytesPerFile = maxBytesPerFile; + + try { + // get the directory where the log files are going to go + filepath = makeDirIfRequired(dir); + + switchFile(LogTime.nextDay()); + + } catch (Exception e) { + System.out.println("FATAL ERROR: init of FileLogger: " + e.getMessage()); + System.err.println("FATAL ERROR: init of FileLogger: " + e.getMessage()); + throw new RuntimeException(e); + } + + logWriterThread = new Thread(this, threadName); + logWriterThread.setDaemon(true); + } + + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + public void start() { + logWriterThread.start(); + } + + public void shutdown() { + + shutdown = true; + + synchronized (logWriterThread) { + try { + // wait max 20 seconds + logWriterThread.wait(20000); + logger.fine("Shutdown LogBufferWriter " + threadName + " shutdownComplete:" + shutdownComplete); + + } catch (InterruptedException e) { + logger.fine("InterruptedException:" + e); + } + } + + if (!shutdownComplete) { + String m = "WARNING: Shutdown of LogBufferWriter " + threadName + " not completed."; + System.err.println(m); + logger.warning(m); + } + + } + + public void run() { + + int missCount = 0; + + while (!shutdown || missCount < 10) { + if (missCount > 50) { + + if (out != null) { + out.flush(); + } + try { + Thread.sleep(20); + } catch (InterruptedException e) { + logger.log(Level.INFO, "Interrupted TxnLogBufferWriter", e); + } + } + synchronized (queueMonitor) { + if (logBufferQueue.isEmpty()) { + ++missCount; + } else { + TransactionLogBuffer buffer = logBufferQueue.remove(); + write(buffer); + missCount = 0; + } + } + } + + close(); + shutdownComplete = true; + + synchronized (logWriterThread) { + logWriterThread.notifyAll(); + } + } + + public void log(TransactionLogBuffer logBuffer) { + logBufferQueue.add(logBuffer); + } + + private void write(TransactionLogBuffer logBuffer) { + + // check to see if we need to switch file? + LogTime logTime = LogTime.get(); + if (logTime.isNextDay()) { + logTime = LogTime.nextDay(); + switchFile(logTime); + } + + if (bytesWritten > maxBytesPerFile) { + ++fileCounter; + switchFile(logTime); + } + + String txnId = logBuffer.getTransactionId(); + + List messages = logBuffer.messages(); + for (int i = 0; i < messages.size(); i++) { + LogEntry msg = messages.get(i); + printMessage(logTime, txnId, msg); + } + } + + private void printMessage(LogTime logTime, String txnId, LogEntry logEntry) { + + String msg = logEntry.getMsg(); + int len = msg.length(); + if (len == 0) { + return; + } + + // add overhead + content + bytesWritten += 16; + bytesWritten += len; + + if (txnId != null) { + bytesWritten += 7; + bytesWritten += txnId.length(); + out.append("txn["); + out.append(txnId); + out.append("]"); + out.append(deliminator); + } + + out.append(logTime.getTimestamp(logEntry.getTimestamp())); + out.append(deliminator); + out.append(msg).append(" "); + out.append("\n"); + } + + /** + * Recursively output the Throwable stack trace to the log. + * + * @param sb + * the buffer to write the stack trace to + * @param e + * the source throwable + * @param isCause + * flag to indicate if this is the top level throwable or a cause + */ + protected void printThrowable(StringBuilder sb, Throwable e, boolean isCause) { + if (e != null) { + if (isCause) { + sb.append("Caused by: "); + } + sb.append(e.getClass().getName()); + sb.append(":"); + sb.append(e.getMessage()).append(newLinePlaceholder); + + StackTraceElement[] ste = e.getStackTrace(); + int outputStackLines = ste.length; + int notShownCount = 0; + if (ste.length > maxStackTraceLines) { + outputStackLines = maxStackTraceLines; + notShownCount = ste.length - outputStackLines; + } + for (int i = 0; i < outputStackLines; i++) { + sb.append(atString); + sb.append(ste[i].toString()).append(newLinePlaceholder); + } + if (notShownCount > 0) { + sb.append(" ... "); + sb.append(notShownCount); + sb.append(" more").append(newLinePlaceholder); + } + Throwable cause = e.getCause(); + if (cause != null) { + printThrowable(sb, cause, true); + } + } + } + + private String newFileName(LogTime logTime) { + return filepath + File.separator + logFileName + logTime.getYMD() + "-" + fileCounter + logFileSuffix; + } + + /** + * Switch the file to log to. + */ + protected void switchFile(LogTime logTime) { + + try { + long currentFileLength = 0; + String newFilePath = null; + + // skip a file if it already has max bytes + do { + newFilePath = newFileName(logTime); + File f = new File(newFilePath); + if (!f.exists()) { + currentFileLength = 0; + } else { + if (f.length() < maxBytesPerFile * 0.8) { + currentFileLength = f.length(); + } else { + ++fileCounter; + newFilePath = null; + } + } + } while (newFilePath == null); + + if (!newFilePath.equals(currentPath)) { + PrintStream newOut = new PrintStream(new BufferedOutputStream(new FileOutputStream(newFilePath, true))); + + close(); + + bytesWritten = currentFileLength; + currentPath = newFilePath; + out = newOut; + } + + } catch (IOException e) { + e.printStackTrace(); + logger.log(Level.SEVERE, "Error switch log file", e); + } + } + + /** + * Close the file output stream being used for logging. + */ + private void close() { + if (out != null) { + out.flush(); + out.close(); + } + } + + /** + * Returns the directory path of the log file. + */ + protected String makeDirIfRequired(String dir) { + + File f = new File(dir); + if (f.exists()) { + if (!f.isDirectory()) { + String msg = "Transaction logs directory is a file? " + dir; + throw new PersistenceException(msg); + } + } else { + if (!f.mkdirs()) { + String msg = "Failed to create transaction logs directory " + dir; + logger.log(Level.SEVERE, msg); + } + } + return dir; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLoggerWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLoggerWrapper.java new file mode 100644 index 000000000..dbc6f95e2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/FileTransactionLoggerWrapper.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction.log; + +import java.util.logging.Logger; + +import com.avaje.ebean.config.GlobalProperties; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.transaction.TransactionLogBuffer; +import com.avaje.ebeaninternal.server.transaction.TransactionLogWriter; + +/** + * Wraps a FileTransactionLogger to provide delayed initialisation. + *

    + * This means that it should only create the log file WHEN there is something + * actually logged. This means the logLevel can start at NONE and then change at + * runtime (and the log file will then be initialised). NOTE that a volatile + * with double checked locking is used to make this transition thread safe. + *

    + * + * @author rbygrave + * + */ +public class FileTransactionLoggerWrapper implements TransactionLogWriter { + + private static final Logger logger = Logger.getLogger(FileTransactionLoggerWrapper.class.getName()); + + private final String serverName; + private final String dir; + private final int maxFileSize; + + private volatile FileTransactionLogger logWriter; + + public FileTransactionLoggerWrapper(ServerConfig serverConfig) { + + String evalDir = serverConfig.getLoggingDirectoryWithEval(); + this.dir = evalDir != null ? evalDir : "logs"; + this.maxFileSize = GlobalProperties.getInt("ebean.logging.maxFileSize", 100 * 1024 * 1024); + this.serverName = serverConfig.getName(); + } + + private FileTransactionLogger initialiseLogger() { + + synchronized (this) { + // double check locking here so logWriter NEEDS to be volatile!! + FileTransactionLogger writer = this.logWriter; + if (writer != null) { + return writer; + } + + String middleName = GlobalProperties.get("ebean.logging.filename", "_txn_"); + String logPrefix = serverName + middleName; + String threadName = "Ebean-" + serverName + "-TxnLogWriter"; + + // create the real logger and start it + FileTransactionLogger newLogWriter = new FileTransactionLogger(threadName, dir, logPrefix, maxFileSize); + + // assignment of volatile field + this.logWriter = newLogWriter; + + // start background thread for the writer + newLogWriter.start(); + logger.info("Transaction logs in: " + dir); + return newLogWriter; + } + } + + public void log(TransactionLogBuffer logBuffer) { + // volatile read + FileTransactionLogger writer = this.logWriter; + if (writer == null) { + writer = initialiseLogger(); + } + writer.log(logBuffer); + } + + public void shutdown() { + if (logWriter != null) { + logWriter.shutdown(); + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/log/JuliTransactionLogger.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/JuliTransactionLogger.java new file mode 100644 index 000000000..4bd35e3c5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/JuliTransactionLogger.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction.log; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.transaction.TransactionLogBuffer; +import com.avaje.ebeaninternal.server.transaction.TransactionLogWriter; +import com.avaje.ebeaninternal.server.transaction.TransactionLogBuffer.LogEntry; + +/** + * A transactionLogger that uses a java.util.logging.Logger. + *

    + * See {@link ServerConfig#setUseJuliTransactionLogger(boolean)} + *

    + * @author rbygrave + */ +public class JuliTransactionLogger implements TransactionLogWriter { + + private static Logger logger = Logger.getLogger(JuliTransactionLogger.class.getName()); + + public void log(TransactionLogBuffer logBuffer) { + + String txnId = logBuffer.getTransactionId(); + + List messages = logBuffer.messages(); + for (int i = 0; i < messages.size(); i++) { + LogEntry logEntry = messages.get(i); + log(txnId, logEntry); + } + } + + public void shutdown() { + } + + private void log(String txnId, LogEntry entry) { + + String message = entry.getMsg(); + if (txnId != null && message != null && !message.startsWith("Trans[")){ + message = "Trans["+txnId+"] "+message; + } + + logger.log(Level.INFO, message); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/log/LogTime.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/LogTime.java new file mode 100644 index 000000000..c9175ea1f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/LogTime.java @@ -0,0 +1,192 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction.log; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +/** + * Utility object used to in logging time. + *

    + * Used in place of SimpleDateFormat which requires synchronization. Determines + * if the day has changed and returns a format of the current time and day. + *

    + */ +public class LogTime { + + private static final String[] sep = { ":", "." }; + + private static LogTime day; + static { + day = new LogTime(); + } + + public static LogTime get() { + return day; + } + + public static LogTime nextDay() { + LogTime d = new LogTime(); + day = d; + return d; + } + + public static LogTime getWithCheck() { + LogTime d = day; + if (d.isNextDay()) { + return nextDay(); + } else { + return d; + } + } + + private final String ymd; + + private final long startMidnight; + + private final long startTomorrow; + + /** + * Because every variable is private final the constructor should be thread + * safe (In JDK5+). + */ + private LogTime() { + + GregorianCalendar now = new GregorianCalendar(); + + now.set(Calendar.HOUR_OF_DAY, 0); + now.set(Calendar.MINUTE, 0); + now.set(Calendar.SECOND, 0); + now.set(Calendar.MILLISECOND, 0); + + this.startMidnight = now.getTime().getTime(); + this.ymd = getDayDerived(now); + + now.add(Calendar.DATE, 1); + this.startTomorrow = now.getTime().getTime(); + } + + /** + * Return true if we have moved into tomorrow. This is used to trigger log + * switching if required. + */ + public boolean isNextDay() { + return (System.currentTimeMillis() >= startTomorrow); + } + + /** + * Return the Year Month Day for today. + */ + public String getYMD() { + return ymd; + } + + /** + * Return the current time specify the separators. + *

    + * The separators is a String[2] with the first string separating hours + * minutes and seconds and the second separating seconds from millis. + *

    + *

    + * The default is {":","."} + *

    + */ + public String getNow(String[] separators) { + + return getTimestamp(System.currentTimeMillis(), separators); + + } + + public String getTimestamp(long systime) { + + StringBuilder sb = new StringBuilder(); + getTime(sb, systime, startMidnight, sep); + return sb.toString(); + } + + public String getTimestamp(long systime, String[] separators) { + + StringBuilder sb = new StringBuilder(); + getTime(sb, systime, startMidnight, separators); + return sb.toString(); + } + + /** + * Returns the current time. + *

    + * Format used is hours:minutes:seconds.millis + *

    + */ + public String getNow() { + return getNow(sep); + } + + /** + * Set the derived day information. + */ + private String getDayDerived(Calendar now) { + + int nowyear = now.get(Calendar.YEAR); + int nowmonth = now.get(Calendar.MONTH); + int nowday = now.get(Calendar.DAY_OF_MONTH); + + nowmonth++; + + StringBuilder sb = new StringBuilder(); + + format(sb, nowyear, 4); + format(sb, nowmonth, 2); + format(sb, nowday, 2); + + return sb.toString(); + } + + private void getTime(StringBuilder sb, long time, long midnight, String[] separator) { + + long rem = time - midnight;// startMidnight; + + long millis = rem % 1000; + rem = rem / 1000; + long secs = rem % 60; + rem = rem / 60; + long mins = rem % 60; + rem = rem / 60; + long hrs = rem; + + format(sb, hrs, 2); + sb.append(separator[0]); + format(sb, mins, 2); + sb.append(separator[0]); + format(sb, secs, 2); + sb.append(separator[1]); + format(sb, millis, 3); + } + + private void format(StringBuilder sb, long value, int places) { + String format = Long.toString(value); + + int pad = places - format.length(); + for (int i = 0; i < pad; i++) { + sb.append("0"); + } + sb.append(format); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/log/SimpleLogger.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/SimpleLogger.java new file mode 100644 index 000000000..5b7df335f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/SimpleLogger.java @@ -0,0 +1,322 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.transaction.log; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.persistence.PersistenceException; + + +/** + * File based logger that can switch daily. It will include the date in the + * files name. + *

    + * Administration Note: If log file switching fails it will send the error to + * standard out and standard err print streams. This is assumed to be rather + * unlikely but possible. + *

    + */ +public class SimpleLogger { + + private static final Logger logger = Logger.getLogger(SimpleLogger.class.getName()); + + /** + * Used to print stack trace. + */ + private static final String atString = " at "; + + /** + * The output stream. + */ + private PrintStream out; + + /** + * Whether to append or replace. + */ + private boolean doAppend = true; + + /** + * Flag to indicate the logger if the logger has been closed. + */ + private boolean open = true; + + /** + * The current file path. + */ + private String currentPath; + + /** + * The path of the log file. + */ + private final String filepath; + + /** + * Set to true if using daily file switching. + */ + private final boolean useFileSwitching; + + /** + * The maximum number of stack lines to output. + * This is just for transaction logging so 5 is fine. + */ + private final int maxStackTraceLines = 5; + + /** + * The delimiter to use. + */ + private final String deliminator; + + /** + * Object used to synch the file switching. + */ + private final Object fileMonitor = new Object(); + + /** + * The prefix of the log file name. + */ + private final String logFileName; + + /** + * The file suffix for the logs. + */ + private final String logFileSuffix; + + /** + * The newLineChar used instead of NL or CRNL. + */ + private final String newLineChar = "\\r\\n"; + + private final boolean csv; + + /** + * Create a logger with a logFileName and useFileSwitching flag. + * + * @param dir + * the sub directory to put the log. Can be null. + * @param logFileName + * the prefix log file name. + * @param useFileSwitching + * if true then use daily file switching. + */ + public SimpleLogger(String dir, String logFileName, boolean useFileSwitching, String suffix) { + this.logFileName = logFileName; + this.useFileSwitching = useFileSwitching; + this.logFileSuffix = "."+suffix; + csv = "csv".equalsIgnoreCase(suffix); + deliminator = csv ? "," : ", "; + try { + + // get the directory where the log files are going to go + filepath = makeDirIfRequired(dir); + + switchFile(LogTime.nextDay()); + + } catch (Exception e) { + // Not going to use logger to show logger exceptions... + // Using standard out and standard err instead. + System.out.println("FATAL ERROR: init of FileLogger: " + e.getMessage()); + System.err.println("FATAL ERROR: init of FileLogger: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + public SimpleLogger(String dir, String logFileName, boolean useFileSwitching) { + this(dir, logFileName, useFileSwitching, "log"); + } + + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + /** + * Close the file output stream being used for logging. + */ + public void close() { + if (open) { + out.flush(); + out.close(); + open = false; + } + } + + public void log(String msg) { + log(null, msg, null); + } + + public void log(String msg, Throwable e) { + log(null, msg, e); + } + + /** + * Log the event to the file. + */ + public void log(String transId, String msg, Throwable e) { + + // check to see if we need to switch file? + LogTime logTime = LogTime.get(); + if (logTime.isNextDay()) { + logTime = LogTime.nextDay(); + try { + switchFile(logTime); + } catch (Exception ex) { + // This is a pretty serious error... not logging it though as + // this could be recursive so just going to send to std err + ex.printStackTrace(); + } + } + + // prefix of transID and timestamp ~ 40 chars + int roughSize = 40; + if (msg != null) { + roughSize += msg.length(); + } + if (e != null) { + roughSize += 200; + } + + StringBuilder line = new StringBuilder(roughSize); + if (transId != null){ + line.append("trans[").append(transId).append("]").append(deliminator); + } + + if (csv){ + line.append("\"'"); + } + line.append(logTime.getNow()); + if (csv){ + line.append("'\""); + } + line.append(deliminator); + + if (msg != null) { + line.append(msg).append(" "); + } + + printThrowable(line, e, false); + + String lineString = line.toString(); + + synchronized (fileMonitor) { + out.println(lineString); + // without flush automatic close() *MUST* be called + out.flush(); + } + } + + /** + * Recursively output the Throwable stack trace to the log. + * + * @param sb + * the buffer to write the stack trace to + * @param e + * the source throwable + * @param isCause + * flag to indicate if this is the top level throwable or a cause + */ + protected void printThrowable(StringBuilder sb, Throwable e, boolean isCause) { + if (e != null) { + if (isCause) { + sb.append("Caused by: "); + } + sb.append(e.getClass().getName()); + sb.append(":"); + sb.append(e.getMessage()).append(newLineChar); + + StackTraceElement[] ste = e.getStackTrace(); + int outputStackLines = ste.length; + int notShownCount = 0; + if (ste.length > maxStackTraceLines) { + outputStackLines = maxStackTraceLines; + notShownCount = ste.length - outputStackLines; + } + for (int i = 0; i < outputStackLines; i++) { + sb.append(atString); + sb.append(ste[i].toString()).append(newLineChar); + } + if (notShownCount > 0) { + sb.append(" ... "); + sb.append(notShownCount); + sb.append(" more").append(newLineChar); + } + Throwable cause = e.getCause(); + if (cause != null) { + printThrowable(sb, cause, true); + } + } + } + + /** + * Creates a new file and sets the file logging output to be directed to the + * new file. + * + * @exception Exception + * indicates a problem writing to the new log file. + */ + protected void switchFile(LogTime logTime) throws Exception { + + String newFilePath = filepath + File.separator + logFileName; + + if (useFileSwitching) { + // For file switching include the date in the file name + newFilePath = newFilePath + logTime.getYMD() + logFileSuffix; + + } else { + newFilePath = newFilePath + logFileSuffix; + } + + // Try to open an output stream to the file + synchronized (fileMonitor) { + + if (!newFilePath.equals(currentPath)) { + currentPath = newFilePath; + + out = new PrintStream(new BufferedOutputStream(new FileOutputStream(newFilePath, + doAppend))); + } + } + } + + /** + * Returns the directory path of the log file. + */ + protected String makeDirIfRequired(String dir) { + + File f = new File(dir); + if (f.exists()){ + if (!f.isDirectory()){ + String msg = "Transaction logs directory is a file? "+dir; + throw new PersistenceException(msg); + } + } else { + if (!f.mkdirs()) { + String msg = "Failed to create transaction logs directory "+dir; + logger.log(Level.SEVERE, msg); + } + } + return dir; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/log/package.html b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/package.html new file mode 100644 index 000000000..2ccf02dd6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/log/package.html @@ -0,0 +1,10 @@ + + + + Transaction Logging + + +Transaction logging + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/package.html b/src/main/java/com/avaje/ebeaninternal/server/transaction/package.html new file mode 100644 index 000000000..528760f43 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/package.html @@ -0,0 +1,14 @@ + + + + Transaction implementation + + +Transaction implementation + +

    +Implementation of the transaction functions. +

    + + + \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/BeanToDbMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/BeanToDbMap.java new file mode 100644 index 000000000..ccf908f1b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/BeanToDbMap.java @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.util.HashMap; + +/** + * Used to map Bean values to DB values. + *

    + * Useful for building Enum converters where you want to map the DB values an + * Enum gets converter to. + *

    + * + * @param + * The Bean value type + * @param + * The DB value type + */ +public class BeanToDbMap { + + final HashMap keyMap; + + final HashMap valueMap; + + final boolean allowNulls; + + /** + * Construct with allowNulls defaulting to false. + */ + public BeanToDbMap() { + this(false); + } + + /** + * Construct with allowNulls setting. + *

    + * If allowNulls is false then an IllegalArgumentException is thrown by + * either the getDBValue or getBeanValue methods if not matching Bean or DB + * value is found. + *

    + */ + public BeanToDbMap(boolean allowNulls) { + this.allowNulls = allowNulls; + keyMap = new HashMap(); + valueMap = new HashMap(); + } + + /** + * Add a bean value and DB value pair. + */ + public BeanToDbMap add(B beanValue, D dbValue) { + keyMap.put(beanValue, dbValue); + valueMap.put(dbValue, beanValue); + return this; + } + + /** + * Return the DB value given the bean value. + */ + public D getDbValue(B beanValue) { + if (beanValue == null){ + return null; + } + D dbValue = keyMap.get(beanValue); + if (dbValue == null && !allowNulls) { + String msg = "DB value for " + beanValue + " not found in "+valueMap; + throw new IllegalArgumentException(msg); + } + return dbValue; + } + + /** + * Return the Bean value given the DB value. + */ + public B getBeanValue(D dbValue) { + if (dbValue == null){ + return null; + } + B beanValue = valueMap.get(dbValue); + if (beanValue == null && !allowNulls) { + String msg = "Bean value for " + dbValue + " not found in "+valueMap; + throw new IllegalArgumentException(msg); + } + return beanValue; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundProperty.java b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundProperty.java new file mode 100644 index 000000000..a28c95ba7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundProperty.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import com.avaje.ebean.config.CompoundTypeProperty; + +/** + * Wraps a CompoundTypeProperty with it's type and parent for nested compound + * types. + * + * @author rbygrave + */ +public class CtCompoundProperty { + + private final String relativeName; + + private final CtCompoundProperty parent; + + private final CtCompoundType compoundType; + + @SuppressWarnings({ "rawtypes" }) + private final CompoundTypeProperty property; + + public CtCompoundProperty(String relativeName, CtCompoundProperty parent, CtCompoundType ctType, + CompoundTypeProperty property) { + + this.relativeName = relativeName; + this.parent = parent; + this.compoundType = ctType; + this.property = property; + } + + /** + * The property name relative to the root of the compound type. + */ + public String getRelativeName() { + return relativeName; + } + + /** + * The property name local to its type. + */ + public String getPropertyName() { + return property.getName(); + } + + public String toString() { + return relativeName; + } + + @SuppressWarnings("unchecked") + public Object getValue(Object valueObject) { + if (valueObject == null) { + return null; + } + if (parent != null) { + valueObject = parent.getValue(valueObject); + } + return property.getValue(valueObject); + } + + /** + * Set a scalar value that is used to build the immutable compound value + * object. + *

    + * When all the scalar values have been collected then the compound value + * object is built and this can be recursive for nested compound types. + *

    + */ + public Object setValue(Object bean, Object value) { + + // compoundType and propertyName should be correct depth + Object compoundValue = ImmutableCompoundTypeBuilder.set(compoundType, property.getName(), value); + + if (compoundValue != null && parent != null) { + // Continue up the tree + return parent.setValue(bean, compoundValue); + + } else { + return compoundValue; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java new file mode 100644 index 000000000..bd224745a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java @@ -0,0 +1,158 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import com.avaje.ebean.text.StringFormatter; +import com.avaje.ebean.text.StringParser; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Adapter for CtCompoundProperty to 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 int deployOrder; + + public CtCompoundPropertyElAdapter(CtCompoundProperty prop) { + this.prop = prop; + } + + public void setDeployOrder(int deployOrder) { + this.deployOrder = deployOrder; + } + + public Object elConvertType(Object value) { + return value; + } + + public Object elGetReference(Object bean) { + return bean; + } + + public Object elGetValue(Object bean) { + return prop.getValue(bean); + } + + public void elSetReference(Object bean) { + // prop.setValue(bean, value) + } + + public void elSetValue(Object bean, Object value, boolean populate, boolean reference) { + prop.setValue(bean, value); + } + + public int getDeployOrder() { + return deployOrder; + } + + public String getAssocOneIdExpr(String prefix, String operator) { + throw new RuntimeException("Not Supported or Expected"); + } + + public Object[] getAssocOneIdValues(Object bean) { + throw new RuntimeException("Not Supported or Expected"); + } + + public String getAssocIdInExpr(String prefix) { + throw new RuntimeException("Not Supported or Expected"); + } + + public String getAssocIdInValueExpr(int size) { + throw new RuntimeException("Not Supported or Expected"); + } + + public BeanProperty getBeanProperty() { + return null; + } + + public StringFormatter getStringFormatter() { + return null; + } + + public StringParser getStringParser() { + return null; + } + + public boolean isDbEncrypted() { + return false; + } + + public boolean isLocalEncrypted() { + return false; + } + + public boolean isAssocId() { + return false; + } + + public boolean isAssocProperty() { + return false; + } + + public boolean isDateTimeCapable() { + return false; + } + + public int getJdbcType() { + return 0; + } + + public Object parseDateTime(long systemTimeMillis) { + throw new RuntimeException("Not Supported or Expected"); + } + + 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/CtCompoundType.java b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundType.java new file mode 100644 index 000000000..ba6547d19 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundType.java @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.avaje.ebean.config.CompoundType; +import com.avaje.ebean.config.CompoundTypeProperty; +import com.avaje.ebean.text.json.JsonElement; +import com.avaje.ebean.text.json.JsonElementObject; +import com.avaje.ebeaninternal.server.text.json.ReadJsonContext; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext; +import com.avaje.ebeaninternal.server.text.json.WriteJsonContext.WriteBeanState; + +/** + * The internal representation of a Compound Type (Immutable Compound Value + * Object). + * + * @author rbygrave + * + * @param + * The Type of the "Immutable Compound Value Object". + */ +public final class CtCompoundType implements ScalarDataReader { + + private final Class cvoClass; + + private final CompoundType cvoType; + + private final Map> propertyMap; + + private final ScalarDataReader[] propReaders; + + private final CompoundTypeProperty[] properties; + + public CtCompoundType(Class cvoClass, CompoundType cvoType, ScalarDataReader[] propReaders) { + + this.cvoClass = cvoClass; + this.cvoType = cvoType; + this.properties = cvoType.getProperties(); + this.propReaders = propReaders; + + this.propertyMap = new LinkedHashMap>(); + for (CompoundTypeProperty cp: properties) { + propertyMap.put(cp.getName(), cp); + } + } + + public String toString() { + return cvoClass.toString(); + } + + public Class getCompoundTypeClass() { + return cvoClass; + } + + public V create(Object[] propertyValues) { + return cvoType.create(propertyValues); + } + + + public V create(Map valueMap) { + + if (valueMap.size() != properties.length) { + // not enough elements in the map + return null; + } + + // we expect the map to contain a value for + // each property and that the values are the + // correct type + Object[] propertyValues = new Object[properties.length]; + for (int i = 0; i < properties.length; i++) { + propertyValues[i] = valueMap.get(properties[i].getName()); + if (propertyValues[i] == null) { + String m = "Null value for " + properties[i].getName() + " in map " + valueMap; + throw new RuntimeException(m); + } + } + + return create(propertyValues); + } + + public CompoundTypeProperty[] getProperties() { + + return cvoType.getProperties(); + } + + public Object[] getPropertyValues(V valueObject) { + + Object[] values = new Object[properties.length]; + for (int i = 0; i < properties.length; i++) { + values[i] = properties[i].getValue(valueObject); + } + return values; + } + + public V read(DataReader source) throws SQLException { + + boolean nullValue = false; + Object[] values = new Object[propReaders.length]; + + for (int i = 0; i < propReaders.length; i++) { + Object o = propReaders[i].read(source); + values[i] = o; + if (o == null){ + nullValue = true; + } + } + + if (nullValue){ + return null; + } + + return create(values); + } + + public void loadIgnore(DataReader dataReader) { + for (int i = 0; i < propReaders.length; i++) { + propReaders[i].loadIgnore(dataReader); + } + } + + public void bind(DataBind b, V value) throws SQLException { + + CompoundTypeProperty[] props = cvoType.getProperties(); + + for (int i = 0; i < props.length; i++) { + Object o = props[i].getValue(value); + propReaders[i].bind(b, o); + } + } + + /** + * Recursively accumulate all the scalar types (in depth first order). + *

    + * This creates a flat list of scalars even when compound types are embedded + * inside compound types. + *

    + */ + public void accumulateScalarTypes(String parent, CtCompoundTypeScalarList list) { + + CompoundTypeProperty[] props = cvoType.getProperties(); + + for (int i = 0; i < propReaders.length; i++) { + String propName = getFullPropName(parent, props[i].getName()); + + list.addCompoundProperty(propName, this, props[i]); + + propReaders[i].accumulateScalarTypes(propName, list); + } + + } + + /** + * Return the full property name (for compound types embedded in other + * compound types). + * + * @param parent + * the parent property name + * @param propName + * the local property name + */ + private String getFullPropName(String parent, String propName) { + if (parent == null) { + return propName; + } else { + return parent + "." + propName; + } + } + + public Object jsonRead(ReadJsonContext ctx) { + + if (!ctx.readObjectBegin()) { + // the object is null + return null; + } + + JsonElementObject jsonObject = new JsonElementObject(); + do { + if (!ctx.readKeyNext()){ + break; + } else { + // we read a property key ... + String propName = ctx.getTokenKey(); + JsonElement unmappedJson = ctx.readUnmappedJson(propName); + jsonObject.put(propName, unmappedJson); + + if (!ctx.readValueNext()){ + break; + } + } + } while(true); + + return readJsonElementObject(ctx, jsonObject); + } + + private Object readJsonElementObject(ReadJsonContext ctx, JsonElementObject jsonObject){ + + boolean nullValue = false; + Object[] values = new Object[propReaders.length]; + + for (int i = 0; i < propReaders.length; i++) { + String propName = properties[i].getName(); + JsonElement jsonElement = jsonObject.get(propName); + + if (propReaders[i] instanceof CtCompoundType) { + values[i] = ((CtCompoundType)propReaders[i]).readJsonElementObject(ctx, (JsonElementObject)jsonElement); + + } else { + values[i] = ((ScalarType)propReaders[i]).jsonFromString(jsonElement.toPrimitiveString(), ctx.getValueAdapter()); + } + if (values[i] == null){ + nullValue = true; + } + } + + if (nullValue){ + return null; + } + + return create(values); + } + + + public void jsonWrite(WriteJsonContext ctx, Object valueObject, String propertyName) { + + if (valueObject == null){ + ctx.beginAssocOneIsNull(propertyName); + + } else { + ctx.pushParentBean(valueObject); + ctx.beginAssocOne(propertyName); + jsonWriteProps(ctx, valueObject, propertyName); + ctx.endAssocOne(); + ctx.popParentBean(); + } + } + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void jsonWriteProps(WriteJsonContext ctx, Object valueObject, String propertyName) { + + ctx.appendObjectBegin(); + WriteBeanState prevState = ctx.pushBeanState(valueObject); + + for (int i = 0; i < properties.length; i++) { + String propName = properties[i].getName(); + Object value = properties[i].getValue((V)valueObject); + if (propReaders[i] instanceof CtCompoundType) { + ((CtCompoundType)propReaders[i]).jsonWrite(ctx, value, propName); + + } else { + ctx.appendNameValue(propName, (ScalarType)propReaders[i], value); + } + } + + ctx.pushPreviousState(prevState); + ctx.appendObjectEnd(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundTypeScalarList.java b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundTypeScalarList.java new file mode 100644 index 000000000..6125483de --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundTypeScalarList.java @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; +import java.util.Map.Entry; + +import com.avaje.ebean.config.CompoundTypeProperty; +import com.avaje.ebeaninternal.server.query.SplitName; + +/** + * Used to build a flat list of all the scalar types nested in a compound type. + * + * @author rbygrave + */ +public final class CtCompoundTypeScalarList { + + private final LinkedHashMap> scalarProps = new LinkedHashMap>(); + + private final LinkedHashMap compoundProperties = new LinkedHashMap(); + + /** + * Return the list of non-scalar properties. These occur when compound types are nested. + */ + public List getNonScalarProperties() { + + List nonScalarProps = new ArrayList(); + + for (String propKey: compoundProperties.keySet()) { + if (!scalarProps.containsKey(propKey)){ + nonScalarProps.add(compoundProperties.get(propKey)); + } + } + + return nonScalarProps; + } + + /** + * Register a property with it's associated compound type and relative name. + */ + public void addCompoundProperty(String propName, CtCompoundType t, CompoundTypeProperty prop) { + + CtCompoundProperty parent = null; + String[] split = SplitName.split(propName); + if (split[0] != null){ + parent = compoundProperties.get(split[0]); + } + + CtCompoundProperty p = new CtCompoundProperty(propName, parent, t, prop); + compoundProperties.put(propName, p); + } + + /** + * Register a scalarType used in the compound type with its given property name. + */ + public void addScalarType(String propName, ScalarType scalar){ + scalarProps.put(propName, scalar); + } + + public CtCompoundProperty getCompoundType(String propName) { + return compoundProperties.get(propName); + } + + public Set>> entries() { + return scalarProps.entrySet(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java b/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java new file mode 100644 index 000000000..8f15bdb8b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java @@ -0,0 +1,147 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.ByteArrayInputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; + +public class DataBind { + + private final PreparedStatement pstmt; + + private int pos; + + public DataBind(PreparedStatement pstmt) { + this.pstmt = pstmt; + } + + public void close() throws SQLException { + pstmt.close(); + } + + public int currentPos() { + return pos; + } + + public void resetPos() { + pos = 0; + } + + public void setObject(Object value) throws SQLException { + pstmt.setObject(++pos, value); + } + + public void setObject(Object value, int sqlType) throws SQLException { + pstmt.setObject(++pos, value, sqlType); + } + + public void setNull(int jdbcType) throws SQLException { + pstmt.setNull(++pos, jdbcType); + } + + public int nextPos() { + return ++pos; + } + + public int decrementPos() { + return ++pos; + } + + public int executeUpdate() throws SQLException { + return pstmt.executeUpdate(); + } + + public PreparedStatement getPstmt() { + return pstmt; + } + + public void setString(String s) throws SQLException { + pstmt.setString(++pos, s); + } + + public void setInt(int i) throws SQLException { + pstmt.setInt(++pos, i); + } + + public void setLong(long i) throws SQLException { + pstmt.setLong(++pos, i); + } + + public void setShort(short i) throws SQLException { + pstmt.setShort(++pos, i); + } + + public void setFloat(float i) throws SQLException { + pstmt.setFloat(++pos, i); + } + + public void setDouble(double i) throws SQLException { + pstmt.setDouble(++pos, i); + } + + public void setBigDecimal(BigDecimal v) throws SQLException { + pstmt.setBigDecimal(++pos, v); + } + + public void setDate(java.sql.Date v) throws SQLException { + pstmt.setDate(++pos, v); + } + + public void setTimestamp(Timestamp v) throws SQLException { + pstmt.setTimestamp(++pos, v); + } + + public void setTime(Time v) throws SQLException { + pstmt.setTime(++pos, v); + } + + public void setBoolean(boolean v) throws SQLException { + pstmt.setBoolean(++pos, v); + } + + public void setBytes(byte[] v) throws SQLException { + pstmt.setBytes(++pos, v); + } + + public void setByte(byte v) throws SQLException { + pstmt.setByte(++pos, v); + } + + public void setChar(char v) throws SQLException { + pstmt.setString(++pos, String.valueOf(v)); + } + + public void setBlob(byte[] bytes) throws SQLException { + ByteArrayInputStream is = new ByteArrayInputStream(bytes); + pstmt.setBinaryStream(++pos, is, bytes.length); + } + + public void setClob(String content) throws SQLException { + Reader reader = new StringReader(content); + pstmt.setCharacterStream(++pos, reader, content.length()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DataEncryptSupport.java b/src/main/java/com/avaje/ebeaninternal/server/type/DataEncryptSupport.java new file mode 100644 index 000000000..8f73faade --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DataEncryptSupport.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import com.avaje.ebean.config.Encryptor; +import com.avaje.ebean.config.EncryptKey; +import com.avaje.ebean.config.EncryptKeyManager; + +public class DataEncryptSupport { + + private final EncryptKeyManager encryptKeyManager; + private final Encryptor encryptor; + private final String table; + private final String column; + + public DataEncryptSupport(EncryptKeyManager encryptKeyManager, Encryptor encryptor, String table, String column) { + this.encryptKeyManager = encryptKeyManager; + this.encryptor = encryptor; + this.table = table; + this.column = column; + } + + public byte[] encrypt(byte[] data){ + + EncryptKey key = encryptKeyManager.getEncryptKey(table, column); + return encryptor.encrypt(data, key); + } + + public byte[] decrypt(byte[] data){ + + EncryptKey key = encryptKeyManager.getEncryptKey(table, column); + return encryptor.decrypt(data, key); + } + + public String decryptObject(byte[] data) { + EncryptKey key = encryptKeyManager.getEncryptKey(table, column); + return encryptor.decryptString(data, key); + } + + public byte[] encryptObject(String formattedValue) { + EncryptKey key = encryptKeyManager.getEncryptKey(table, column); + return encryptor.encryptString(formattedValue, key); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DataReader.java b/src/main/java/com/avaje/ebeaninternal/server/type/DataReader.java new file mode 100644 index 000000000..2950273d4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DataReader.java @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.SQLException; + +public interface DataReader { + + public void close() throws SQLException; + + public boolean next() throws SQLException; + + public void resetColumnPosition(); + + public void incrementPos(int increment); + + public byte[] getBinaryBytes() throws SQLException; + + public byte[] getBlobBytes() throws SQLException; + + public String getStringFromStream() throws SQLException; + + public String getStringClob() throws SQLException; + + public String getString() throws SQLException; + + public Boolean getBoolean() throws SQLException; + + public Byte getByte() throws SQLException; + + public Short getShort() throws SQLException; + + public Integer getInt() throws SQLException; + + public Long getLong() throws SQLException; + + public Float getFloat() throws SQLException; + + public Double getDouble() throws SQLException; + + public byte[] getBytes() throws SQLException; + + public java.sql.Date getDate() throws SQLException; + + public java.sql.Time getTime() throws SQLException; + + public java.sql.Timestamp getTimestamp() throws SQLException; + + public BigDecimal getBigDecimal() throws SQLException; + + public Array getArray() throws SQLException; + + public Object getObject() throws SQLException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeFactory.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeFactory.java new file mode 100644 index 000000000..085ffb818 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeFactory.java @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.math.BigInteger; +import java.sql.Types; +import java.util.Calendar; + +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * Helper to create some default ScalarType objects for Booleans, + * java.util.Date, java.util.Calendar etc. + */ +public class DefaultTypeFactory { + + private final ServerConfig serverConfig; + + public DefaultTypeFactory(ServerConfig serverConfig) { + this.serverConfig = serverConfig; + } + + private ScalarType createBoolean(String trueValue, String falseValue) { + + try { + // first try Integer based boolean + Integer intTrue = BasicTypeConverter.toInteger(trueValue); + Integer intFalse = BasicTypeConverter.toInteger(falseValue); + + return new ScalarTypeBoolean.IntBoolean(intTrue, intFalse); + + } catch (NumberFormatException e) { + } + + // treat as Varchar/String based boolean + return new ScalarTypeBoolean.StringBoolean(trueValue, falseValue); + + } + + /** + * Create the ScalarType for mapping Booleans. For some databases this is a + * native data type and for others Booleans will be converted to Y/N or 0/1 + * etc. + */ + public ScalarType createBoolean() { + + if (serverConfig == null) { + return new ScalarTypeBoolean.Native(); + } + String trueValue = serverConfig.getDatabaseBooleanTrue(); + String falseValue = serverConfig.getDatabaseBooleanFalse(); + + if (falseValue != null && trueValue != null) { + // explicit integer or string based booleans + return createBoolean(trueValue, falseValue); + } + + // determine based on database platform configuration + int booleanDbType = serverConfig.getDatabasePlatform().getBooleanDbType(); + + // Some dbs use BIT e.g. MySQL + if (booleanDbType == Types.BIT) { + return new ScalarTypeBoolean.BitBoolean(); + } + + if (booleanDbType == Types.INTEGER) { + return new ScalarTypeBoolean.IntBoolean(1, 0); + } + if (booleanDbType == Types.VARCHAR) { + return new ScalarTypeBoolean.StringBoolean("T", "F"); + } + + if (booleanDbType == Types.BOOLEAN) { + return new ScalarTypeBoolean.Native(); + } + + // assume the JDBC driver can convert the type + return new ScalarTypeBoolean.Native(); + } + + /** + * Create the default ScalarType for java.util.Date. + */ + public ScalarType createUtilDate() { + // by default map anonymous java.util.Date to java.sql.Timestamp. + // String mapType = + // properties.getProperty("type.mapping.java.util.Date","timestamp"); + int utilDateType = getTemporalMapType("timestamp"); + + return createUtilDate(utilDateType); + } + + /** + * Create a ScalarType for java.util.Date explicitly specifying the type to + * map to. + */ + public ScalarType createUtilDate(int utilDateType) { + + switch (utilDateType) { + case Types.DATE: + return new ScalarTypeUtilDate.DateType(); + + case Types.TIMESTAMP: + return new ScalarTypeUtilDate.TimestampType(); + + default: + throw new RuntimeException("Invalid type " + utilDateType); + } + } + + /** + * Create the default ScalarType for java.util.Calendar. + */ + public ScalarType createCalendar() { + // by default map anonymous java.util.Calendar to java.sql.Timestamp. + // String mapType = + // properties.getProperty("type.mapping.java.util.Calendar", + // "timestamp"); + int jdbcType = getTemporalMapType("timestamp"); + + return createCalendar(jdbcType); + } + + /** + * Create a ScalarType for java.util.Calendar explicitly specifying the type + * to map to. + */ + public ScalarType createCalendar(int jdbcType) { + + return new ScalarTypeCalendar(jdbcType); + } + + private int getTemporalMapType(String mapType) { + if (mapType.equalsIgnoreCase("date")) { + return java.sql.Types.DATE; + } + return java.sql.Types.TIMESTAMP; + } + + /** + * Create a ScalarType for java.math.BigInteger. + */ + public ScalarType createMathBigInteger() { + + return new ScalarTypeMathBigInteger(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java new file mode 100644 index 000000000..606de24de --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -0,0 +1,748 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URL; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Currency; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.joda.time.DateMidnight; +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.joda.time.LocalTime; + +import com.avaje.ebean.annotation.EnumMapping; +import com.avaje.ebean.annotation.EnumValue; +import com.avaje.ebean.config.CompoundType; +import com.avaje.ebean.config.CompoundTypeProperty; +import com.avaje.ebean.config.ScalarTypeConverter; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.api.ClassUtil; +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.type.reflect.CheckImmutable; +import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse; +import com.avaje.ebeaninternal.server.type.reflect.ImmutableMeta; +import com.avaje.ebeaninternal.server.type.reflect.ImmutableMetaFactory; +import com.avaje.ebeaninternal.server.type.reflect.KnownImmutable; +import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundType; +import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundTypeProperty; +import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedTypeBuilder; + +/** + * Default implementation of TypeManager. + *

    + * Manages the list of ScalarType that is available. + *

    + */ +public final class DefaultTypeManager implements TypeManager, KnownImmutable { + + private static final Logger logger = Logger.getLogger(DefaultTypeManager.class.getName()); + + private final ConcurrentHashMap, CtCompoundType> compoundTypeMap; + + private final ConcurrentHashMap, ScalarType> typeMap; + + private final ConcurrentHashMap> nativeMap; + + private final DefaultTypeFactory extraTypeFactory; + + private final ScalarType charType = new ScalarTypeChar(); + + private final ScalarType charArrayType = new ScalarTypeCharArray(); + + private final ScalarType longVarcharType = new ScalarTypeLongVarchar(); + + private final ScalarType clobType = new ScalarTypeClob(); + + private final ScalarType byteType = new ScalarTypeByte(); + + private final ScalarType binaryType = new ScalarTypeBytesBinary(); + + private final ScalarType blobType = new ScalarTypeBytesBlob(); + + private final ScalarType varbinaryType = new ScalarTypeBytesVarbinary(); + + private final ScalarType longVarbinaryType = new ScalarTypeBytesLongVarbinary(); + + private final ScalarType shortType = new ScalarTypeShort(); + + private final ScalarType integerType = new ScalarTypeInteger(); + + private final ScalarType longType = new ScalarTypeLong(); + + private final ScalarType doubleType = new ScalarTypeDouble(); + + private final ScalarType floatType = new ScalarTypeFloat(); + + private final ScalarType bigDecimalType = new ScalarTypeBigDecimal(); + + private final ScalarType timeType = new ScalarTypeTime(); + + private final ScalarType dateType = new ScalarTypeDate(); + + private final ScalarType timestampType = new ScalarTypeTimestamp(); + + private final ScalarType uuidType = new ScalarTypeUUID(); + private final ScalarType urlType = new ScalarTypeURL(); + private final ScalarType uriType = new ScalarTypeURI(); + private final ScalarType localeType = new ScalarTypeLocale(); + private final ScalarType currencyType = new ScalarTypeCurrency(); + private final ScalarType timeZoneType = new ScalarTypeTimeZone(); + + private final ScalarType stringType = new ScalarTypeString(); + + private final ScalarType classType = new ScalarTypeClass(); + + private final ScalarTypeLongToTimestamp longToTimestamp = new ScalarTypeLongToTimestamp(); + + private final List> customScalarTypes = new ArrayList>(); + + private final CheckImmutable checkImmutable; + + private final ImmutableMetaFactory immutableMetaFactory = new ImmutableMetaFactory(); + + private final ReflectionBasedTypeBuilder reflectScalarBuilder; + + /** + * Create the DefaultTypeManager. + */ + public DefaultTypeManager(ServerConfig config, BootupClasses bootupClasses) { + + int clobType = config == null ? Types.CLOB : config.getDatabasePlatform().getClobDbType(); + int blobType = config == null ? Types.BLOB : config.getDatabasePlatform().getBlobDbType(); + + this.checkImmutable = new CheckImmutable(this); + this.reflectScalarBuilder = new ReflectionBasedTypeBuilder(this); + + this.compoundTypeMap = new ConcurrentHashMap, CtCompoundType>(); + this.typeMap = new ConcurrentHashMap, ScalarType>(); + this.nativeMap = new ConcurrentHashMap>(); + + this.extraTypeFactory = new DefaultTypeFactory(config); + + initialiseStandard(clobType, blobType); + initialiseJodaTypes(); + + if (bootupClasses != null) { + initialiseCustomScalarTypes(bootupClasses); + initialiseScalarConverters(bootupClasses); + initialiseCompoundTypes(bootupClasses); + } + } + + public boolean isKnownImmutable(Class cls) { + + if (cls == null) { + // superclass from an interface + return true; + } + + if (cls.isPrimitive() || Object.class.equals(cls)) { + return true; + } + + ScalarDataReader scalarDataReader = getScalarDataReader(cls); + return scalarDataReader != null; + } + + public CheckImmutableResponse checkImmutable(Class cls) { + return checkImmutable.checkImmutable(cls); + } + + private ScalarType register(ScalarType st) { + add(st); + logger.fine("Registering ScalarType for " + st.getType() + " implemented using reflection"); + return st; + } + + public ScalarDataReader recursiveCreateScalarDataReader(Class cls) { + + ScalarDataReader scalarReader = getScalarDataReader(cls); + if (scalarReader != null) { + return scalarReader; + } + + ImmutableMeta meta = immutableMetaFactory.createImmutableMeta(cls); + + if (!meta.isCompoundType()) { + return register(reflectScalarBuilder.buildScalarType(meta)); + } + + ReflectionBasedCompoundType compoundType = reflectScalarBuilder.buildCompound(meta); + Class compoundTypeClass = compoundType.getCompoundType(); + + return createCompoundScalarDataReader(compoundTypeClass, compoundType, " using reflection"); + + } + + public ScalarType recursiveCreateScalarTypes(Class cls) { + + ScalarType scalarType = getScalarType(cls); + if (scalarType != null) { + return scalarType; + } + + ImmutableMeta meta = immutableMetaFactory.createImmutableMeta(cls); + + if (!meta.isCompoundType()) { + return register(reflectScalarBuilder.buildScalarType(meta)); + } + + throw new RuntimeException("Not allowed compound types here"); + + } + + /** + * Register a custom ScalarType. + */ + public void add(ScalarType scalarType) { + typeMap.put(scalarType.getType(), scalarType); + logAdd(scalarType); + } + + protected void logAdd(ScalarType scalarType) { + if (logger.isLoggable(Level.FINE)) { + String msg = "ScalarType register [" + scalarType.getClass().getName() + "]"; + msg += " for [" + scalarType.getType().getName() + "]"; + logger.fine(msg); + } + } + + public CtCompoundType getCompoundType(Class type) { + return compoundTypeMap.get(type); + } + + /** + * Return the ScalarType for the given jdbc type as per java.sql.Types. + */ + public ScalarType getScalarType(int jdbcType) { + return nativeMap.get(jdbcType); + } + + /** + * This can return null if no matching ScalarType is found. + */ + @SuppressWarnings("unchecked") + public ScalarType getScalarType(Class type) { + return (ScalarType) typeMap.get(type); + } + + public ScalarDataReader getScalarDataReader(Class propertyType, int sqlType) { + + if (sqlType == 0) { + return recursiveCreateScalarDataReader(propertyType); + } + + for (int i = 0; i < customScalarTypes.size(); i++) { + ScalarType customScalarType = customScalarTypes.get(i); + + if (sqlType == customScalarType.getJdbcType() && (propertyType.equals(customScalarType.getType()))) { + + return customScalarType; + } + } + + String msg = "Unable to find a custom ScalarType with type [" + propertyType + "] and java.sql.Type [" + sqlType + "]"; + throw new RuntimeException(msg); + } + + public ScalarDataReader getScalarDataReader(Class type) { + ScalarDataReader reader = typeMap.get(type); + if (reader == null) { + reader = compoundTypeMap.get(type); + } + return reader; + } + + /** + * Return a ScalarType for a given class. + *

    + * Used for java.util.Date and java.util.Calendar which can be mapped to + * different jdbcTypes in a single system. + *

    + */ + @SuppressWarnings("unchecked") + public ScalarType getScalarType(Class type, int jdbcType) { + + // check for Clob, LongVarchar etc first... + // the reason being that String maps to multiple jdbc types + // varchar, clob, longVarchar. + ScalarType scalarType = getLobTypes(jdbcType); + if (scalarType != null) { + // it is a specific Lob type... + return (ScalarType) scalarType; + } + + scalarType = typeMap.get(type); + if (scalarType != null) { + if (jdbcType == 0 || scalarType.getJdbcType() == jdbcType) { + // matching type + return (ScalarType) scalarType; + } else { + // sometime like java.util.Date or java.util.Calendar + // that that does not map to the same jdbc type as the + // server wide settings. + } + } + // a util Date with jdbcType not matching server wide settings + if (type.equals(java.util.Date.class)) { + return (ScalarType) extraTypeFactory.createUtilDate(jdbcType); + } + // a Calendar with jdbcType not matching server wide settings + if (type.equals(java.util.Calendar.class)) { + return (ScalarType) extraTypeFactory.createCalendar(jdbcType); + } + + String msg = "Unmatched ScalarType for " + type + " jdbcType:" + jdbcType; + throw new RuntimeException(msg); + } + + /** + * Return the types for the known lob types. + *

    + * Kind of special case because these map multiple jdbc types to single Java + * types - like String - Varchar, LongVarchar, Clob. For this reason I check + * for the specific Lob types first before looking for a matching type. + *

    + */ + private ScalarType getLobTypes(int jdbcType) { + + return getScalarType(jdbcType); + } + + /** + * Convert the Object to the required datatype. The + * + * @param value + * the Object value + * @param toJdbcType + * the type as per java.sql.Types. + */ + public Object convert(Object value, int toJdbcType) { + if (value == null) { + return null; + } + ScalarType type = nativeMap.get(toJdbcType); + if (type != null) { + return type.toJdbcType(value); + } + return value; + } + + private boolean isIntegerType(String s) { + + try { + Integer.parseInt(s); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * Create the Mapping of Enum fields to DB values using EnumValue annotations. + *

    + * Return null if the EnumValue annotations are not present/used. + *

    + */ + private ScalarType createEnumScalarType2(Class enumType) { + + boolean integerType = true; + + Map nameValueMap = new HashMap(); + + Field[] fields = enumType.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + EnumValue enumValue = fields[i].getAnnotation(EnumValue.class); + if (enumValue != null) { + nameValueMap.put(fields[i].getName(), enumValue.value()); + if (integerType && !isIntegerType(enumValue.value())) { + // will treat the values as strings + integerType = false; + } + } + } + if (nameValueMap.isEmpty()) { + // Not using EnumValue here + return null; + } + + return createEnumScalarType(enumType, nameValueMap, integerType, 0); + } + + /** + * Create a ScalarType for an Enum that has additional mapping. + *

    + * The reason for this is that often in a DB there will be short codes used + * such as A,I,N rather than the ACTIVE, INACTIVE, NEW. So there really needs + * to be a mapping from the nicely named enumeration values to the typically + * much shorter codes used in the DB. + *

    + */ + public ScalarType createEnumScalarType(Class enumType) { + + // get the mapping information from EnumMapping + EnumMapping enumMapping = (EnumMapping) enumType.getAnnotation(EnumMapping.class); + if (enumMapping == null) { + // look for EnumValue annotations instead + return createEnumScalarType2(enumType); + } + + String nameValuePairs = enumMapping.nameValuePairs(); + boolean integerType = enumMapping.integerType(); + int dbColumnLength = enumMapping.length(); + + Map nameValueMap = StringHelper.delimitedToMap(nameValuePairs, ",", "="); + + return createEnumScalarType(enumType, nameValueMap, integerType, dbColumnLength); + } + + /** + * Given the name value mapping and integer/string type and explicit DB column + * length create the ScalarType for the Enum. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private ScalarType createEnumScalarType(Class enumType, Map nameValueMap, boolean integerType, int dbColumnLength) { + + EnumToDbValueMap beanDbMap = EnumToDbValueMap.create(integerType); + + int maxValueLen = 0; + + Iterator it = nameValueMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + String name = (String) entry.getKey(); + String value = (String) entry.getValue(); + + maxValueLen = Math.max(maxValueLen, value.length()); + + Object enumValue = Enum.valueOf(enumType, name.trim()); + beanDbMap.add(enumValue, value.trim()); + } + + if (dbColumnLength == 0 && !integerType) { + dbColumnLength = maxValueLen; + } + + return new ScalarTypeEnumWithMapping(beanDbMap, enumType, dbColumnLength); + } + + /** + * Automatically find any ScalarTypes by searching through the class path. + *

    + * In avaje.properties define a list of packages in which ScalarTypes are + * found. This will search for any class that implements the ScalarType + * interface and register it with this TypeManager. + *

    + */ + protected void initialiseCustomScalarTypes(BootupClasses bootupClasses) { + + customScalarTypes.add(longToTimestamp); + + List> foundTypes = bootupClasses.getScalarTypes(); + + for (int i = 0; i < foundTypes.size(); i++) { + Class cls = foundTypes.get(i); + try { + + ScalarType scalarType = (ScalarType) cls.newInstance(); + add(scalarType); + + customScalarTypes.add(scalarType); + + } catch (Exception e) { + String msg = "Error loading ScalarType [" + cls.getName() + "]"; + logger.log(Level.SEVERE, msg, e); + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected void initialiseScalarConverters(BootupClasses bootupClasses) { + + List> foundTypes = bootupClasses.getScalarConverters(); + + for (int i = 0; i < foundTypes.size(); i++) { + Class cls = foundTypes.get(i); + try { + + Class[] paramTypes = TypeReflectHelper.getParams(cls, ScalarTypeConverter.class); + if (paramTypes.length != 2) { + throw new IllegalStateException("Expected 2 generics paramtypes but got: " + Arrays.toString(paramTypes)); + } + + Class logicalType = paramTypes[0]; + Class persistType = paramTypes[1]; + + ScalarType wrappedType = getScalarType(persistType); + if (wrappedType == null) { + throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); + } + + ScalarTypeConverter converter = (ScalarTypeConverter) cls.newInstance(); + ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, converter); + + logger.fine("Register ScalarTypeWrapper from " + logicalType + " -> " + persistType + " using:" + cls); + + add(stw); + + } catch (Exception e) { + String msg = "Error loading ScalarType [" + cls.getName() + "]"; + logger.log(Level.SEVERE, msg, e); + } + } + + } + + protected void initialiseCompoundTypes(BootupClasses bootupClasses) { + + ArrayList> compoundTypes = bootupClasses.getCompoundTypes(); + for (int j = 0; j < compoundTypes.size(); j++) { + + Class type = compoundTypes.get(j); + try { + + Class[] paramTypes = TypeReflectHelper.getParams(type, CompoundType.class); + if (paramTypes.length != 1) { + throw new RuntimeException("Expecting 1 generic paramter type but got " + Arrays.toString(paramTypes) + " for " + type); + } + + Class compoundTypeClass = paramTypes[0]; + + CompoundType compoundType = (CompoundType) type.newInstance(); + createCompoundScalarDataReader(compoundTypeClass, compoundType, ""); + + } catch (Exception e) { + String msg = "Error initialising component " + type; + throw new RuntimeException(msg, e); + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected CtCompoundType createCompoundScalarDataReader(Class compoundTypeClass, CompoundType compoundType, String info) { + + CtCompoundType ctCompoundType = compoundTypeMap.get(compoundTypeClass); + if (ctCompoundType != null) { + logger.info("Already registered compound type " + compoundTypeClass); + return ctCompoundType; + } + + CompoundTypeProperty[] cprops = compoundType.getProperties(); + + ScalarDataReader[] dataReaders = new ScalarDataReader[cprops.length]; + + for (int i = 0; i < cprops.length; i++) { + + Class propertyType = getCompoundPropertyType(cprops[i]); + + ScalarDataReader scalarDataReader = getScalarDataReader(propertyType, cprops[i].getDbType()); + if (scalarDataReader == null) { + throw new RuntimeException("Could not find ScalarDataReader for " + propertyType); + } + + dataReaders[i] = scalarDataReader; + } + + CtCompoundType ctType = new CtCompoundType(compoundTypeClass, compoundType, dataReaders); + + logger.fine("Registering CompoundType " + compoundTypeClass + " " + info); + compoundTypeMap.put(compoundTypeClass, ctType); + + return ctType; + } + + /** + * Return the property type for a given property of a compound type. + */ + private Class getCompoundPropertyType(CompoundTypeProperty prop) { + + if (prop instanceof ReflectionBasedCompoundTypeProperty) { + return ((ReflectionBasedCompoundTypeProperty) prop).getPropertyType(); + } + + // determine the types from generic parameter types using reflection + Class[] propParamTypes = TypeReflectHelper.getParams(prop.getClass(), CompoundTypeProperty.class); + if (propParamTypes.length != 2) { + throw new RuntimeException("Expecting 2 generic paramter types but got " + Arrays.toString(propParamTypes) + " for " + + prop.getClass()); + } + + return propParamTypes[1]; + } + + /** + * Detect if Joda classes are in the classpath and if so register the Joda + * data types. + */ + protected void initialiseJodaTypes() { + + // detect if Joda classes are in the classpath + if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) { + // Joda classes are in the classpath so register the types + logger.fine("Registering Joda data types"); + typeMap.put(LocalDateTime.class, new ScalarTypeJodaLocalDateTime()); + typeMap.put(LocalDate.class, new ScalarTypeJodaLocalDate()); + typeMap.put(LocalTime.class, new ScalarTypeJodaLocalTime()); + typeMap.put(DateTime.class, new ScalarTypeJodaDateTime()); + typeMap.put(DateMidnight.class, new ScalarTypeJodaDateMidnight()); + } + } + + /** + * Register all the standard types supported. This is the standard JDBC types + * plus some other common types such as java.util.Date and java.util.Calendar. + */ + protected void initialiseStandard(int platformClobType, int platformBlobType) { + + ScalarType utilDateType = extraTypeFactory.createUtilDate(); + typeMap.put(java.util.Date.class, utilDateType); + + ScalarType calType = extraTypeFactory.createCalendar(); + typeMap.put(Calendar.class, calType); + + ScalarType mathBigIntType = extraTypeFactory.createMathBigInteger(); + typeMap.put(BigInteger.class, mathBigIntType); + + ScalarType booleanType = extraTypeFactory.createBoolean(); + typeMap.put(Boolean.class, booleanType); + typeMap.put(boolean.class, booleanType); + + // always register Types.BOOLEAN to our boolean type + nativeMap.put(Types.BOOLEAN, booleanType); + if (booleanType.getJdbcType() == Types.BIT) { + // for MapBeans ... BIT types are assumed to be booleans + nativeMap.put(Types.BIT, booleanType); + } else { + // boolean mapping to Types.Integer, Types.VARCHAR or Types.Boolean + } + +// ScalarTypeScalaDouble scalaDoubleType = new ScalarTypeScalaDouble(); +// typeMap.put(scala.Double.class, scalaDoubleType); + + typeMap.put(Locale.class, localeType); + typeMap.put(Currency.class, currencyType); + typeMap.put(TimeZone.class, timeZoneType); + typeMap.put(UUID.class, uuidType); + typeMap.put(URL.class, urlType); + typeMap.put(URI.class, uriType); + + // String types + typeMap.put(char[].class, charArrayType); + typeMap.put(char.class, charType); + typeMap.put(String.class, stringType); + nativeMap.put(Types.VARCHAR, stringType); + nativeMap.put(Types.CHAR, stringType); + nativeMap.put(Types.LONGVARCHAR, longVarcharType); + + // Class + typeMap.put(Class.class, classType); + + if (platformClobType == Types.CLOB) { + nativeMap.put(Types.CLOB, clobType); + } else { + // for Postgres Clobs handled by Varchar ScalarType... + ScalarType platClobScalarType = nativeMap.get(Integer.valueOf(platformClobType)); + if (platClobScalarType == null) { + throw new IllegalArgumentException("Type for dbPlatform clobType [" + clobType + "] not found."); + } + nativeMap.put(Types.CLOB, platClobScalarType); + } + + // Binary type + typeMap.put(byte[].class, varbinaryType); + nativeMap.put(Types.BINARY, binaryType); + nativeMap.put(Types.VARBINARY, varbinaryType); + nativeMap.put(Types.LONGVARBINARY, longVarbinaryType); + + if (platformBlobType == Types.BLOB) { + nativeMap.put(Types.BLOB, blobType); + } else { + // for Postgres Blobs handled by LongVarbinary ScalarType... + ScalarType platBlobScalarType = nativeMap.get(Integer.valueOf(platformBlobType)); + if (platBlobScalarType == null) { + throw new IllegalArgumentException("Type for dbPlatform blobType [" + blobType + "] not found."); + } + nativeMap.put(Types.BLOB, platBlobScalarType); + } + + // Number types + typeMap.put(Byte.class, byteType); + typeMap.put(byte.class, byteType); + nativeMap.put(Types.TINYINT, byteType); + + typeMap.put(Short.class, shortType); + typeMap.put(short.class, shortType); + nativeMap.put(Types.SMALLINT, shortType); + + typeMap.put(Integer.class, integerType); + typeMap.put(int.class, integerType); + nativeMap.put(Types.INTEGER, integerType); + + typeMap.put(Long.class, longType); + typeMap.put(long.class, longType); + nativeMap.put(Types.BIGINT, longType); + + typeMap.put(Double.class, doubleType); + typeMap.put(double.class, doubleType); + nativeMap.put(Types.FLOAT, doubleType);// no this is not a bug + nativeMap.put(Types.DOUBLE, doubleType); + + typeMap.put(Float.class, floatType); + typeMap.put(float.class, floatType); + nativeMap.put(Types.REAL, floatType);// no this is not a bug + + typeMap.put(BigDecimal.class, bigDecimalType); + nativeMap.put(Types.DECIMAL, bigDecimalType); + nativeMap.put(Types.NUMERIC, bigDecimalType); + + // Temporal types + typeMap.put(Time.class, timeType); + nativeMap.put(Types.TIME, timeType); + typeMap.put(Date.class, dateType); + nativeMap.put(Types.DATE, dateType); + typeMap.put(Timestamp.class, timestampType); + nativeMap.put(Types.TIMESTAMP, timestampType); + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java new file mode 100644 index 000000000..c4f0dbb20 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java @@ -0,0 +1,56 @@ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +import javax.persistence.PersistenceException; + +/** + * Used to map enum values to database integer values. + */ +public class EnumToDbIntegerMap extends EnumToDbValueMap { + + @Override + public int getDbType() { + return Types.INTEGER; + } + + @Override + public EnumToDbIntegerMap add(Object beanValue, String stringDbValue) { + + try { + Integer value = Integer.valueOf(stringDbValue); + addInternal(beanValue, value); + + return this; + + } catch (Exception e) { + String msg = "Error converted enum type[" + beanValue.getClass().getName(); + msg += "] enum value[" + beanValue + "] string value [" + stringDbValue + "]"; + msg += " to an Integer."; + throw new PersistenceException(msg, e); + } + } + + @Override + public void bind(DataBind b, Object value) throws SQLException { + if (value == null) { + b.setNull(Types.INTEGER); + } else { + Integer s = getDbValue(value); + b.setInt(s); + } + + } + + @Override + public Object read(DataReader dataReader) throws SQLException { + Integer i = dataReader.getInt(); + if (i == null) { + return null; + } else { + return getBeanValue(i); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java new file mode 100644 index 000000000..b8f57bbb9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java @@ -0,0 +1,45 @@ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * Used to map Enum values to database string/varchar values. + */ +public class EnumToDbStringMap extends EnumToDbValueMap { + + + + @Override + public int getDbType() { + return Types.VARCHAR; + } + + @Override + public EnumToDbStringMap add(Object beanValue, String dbValue) { + addInternal(beanValue, dbValue); + return this; + } + + @Override + public void bind(DataBind b, Object value) throws SQLException { + if (value == null){ + b.setNull(Types.VARCHAR); + } else { + String s = getDbValue(value); + b.setString(s); + } + + } + + @Override + public Object read(DataReader dataReader) throws SQLException { + String s = dataReader.getString(); + if (s == null){ + return null; + } else { + return getBeanValue(s); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java new file mode 100644 index 000000000..35676886d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java @@ -0,0 +1,153 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.util.Iterator; +import java.util.LinkedHashMap; + +/** + * Used to map Bean values to DB values. + *

    + * Useful for building Enum converters where you want to map the DB values an + * Enum gets converter to. + *

    + */ +public abstract class EnumToDbValueMap { + + public static EnumToDbValueMap create(boolean integerType) { + return integerType ? new EnumToDbIntegerMap() : new EnumToDbStringMap(); + } + + final LinkedHashMap keyMap; + + final LinkedHashMap valueMap; + + final boolean allowNulls; + + final boolean isIntegerType; + + /** + * Construct with allowNulls defaulting to false. + */ + public EnumToDbValueMap() { + this(false, false); + } + + /** + * Construct with allowNulls setting. + *

    + * If allowNulls is false then an IllegalArgumentException is thrown by + * either the getDBValue or getBeanValue methods if not matching Bean or DB + * value is found. + *

    + */ + public EnumToDbValueMap(boolean allowNulls, boolean isIntegerType) { + this.allowNulls = allowNulls; + this.isIntegerType = isIntegerType; + keyMap = new LinkedHashMap(); + valueMap = new LinkedHashMap(); + } + + /** + * Return true if this is mapping to integers, false + * if mapping to Strings. + */ + public boolean isIntegerType() { + return isIntegerType; + } + + /** + * Return the DB values. + */ + public Iterator dbValues() { + return valueMap.keySet().iterator(); + } + + /** + * Return the bean 'key' value. + */ + public Iterator beanValues() { + return valueMap.values().iterator(); + } + + /** + * Bind using the correct database type. + */ + public abstract void bind(DataBind b, Object value) throws SQLException; + + /** + * Read using the correct database type. + */ + public abstract Object read(DataReader dataReader) throws SQLException; + + /** + * Return the database type. + */ + public abstract int getDbType(); + + /** + * 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); + + /** + * Add a bean value and DB value pair. + *

    + * The dbValue will be converted to an Integer if isIntegerType is true; + *

    + */ + protected void addInternal(Object beanValue, T dbValue) { + + keyMap.put(beanValue, dbValue); + valueMap.put(dbValue, beanValue); + } + + /** + * Return the DB value given the bean value. + */ + public T getDbValue(Object beanValue) { + if (beanValue == null) { + return null; + } + T dbValue = keyMap.get(beanValue); + if (dbValue == null && !allowNulls) { + String msg = "DB value for " + beanValue + " not found in " + valueMap; + throw new IllegalArgumentException(msg); + } + return dbValue; + } + + /** + * Return the Bean value given the DB value. + */ + public Object getBeanValue(T dbValue) { + if (dbValue == null) { + return null; + } + Object beanValue = valueMap.get(dbValue); + if (beanValue == null && !allowNulls) { + String msg = "Bean value for " + dbValue + " not found in " + valueMap; + throw new IllegalArgumentException(msg); + } + return beanValue; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EscapeJson.java b/src/main/java/com/avaje/ebeaninternal/server/type/EscapeJson.java new file mode 100644 index 000000000..01c994ea9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/EscapeJson.java @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.IOException; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +public class EscapeJson { + + /** + * Escape and quote the string value. + */ + public static String escapeQuote(String value) { + if (value == null) { + return "null"; + } + + StringBuilder sb = new StringBuilder(value.length() + 2); + sb.append("\""); + escapeAppend(value, sb); + sb.append("\""); + return sb.toString(); + } + + /** + * Escape quotes, \, /, \r, \n, \b, \f, \t and characters (U+0000 through + * U+001F). + */ + public static String escape(String s) { + if (s == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + escapeAppend(s, sb); + return sb.toString(); + + } + + public static void escape(String value, WriteJsonBuffer sb) { + if (value == null) { + sb.append("null"); + } else { + escapeAppend(value, sb); + } + } + + public static void escapeQuote(String value, WriteJsonBuffer sb) { + if (value == null) { + sb.append("null"); + } else { + sb.append("\""); + escapeAppend(value, sb); + sb.append("\""); + } + } + + /** + * Escape quotes, \, /, \r, \n, \b, \f, \t and characters (U+0000 through + * U+001F). + */ + public static void escapeAppend(String s, Appendable sb) { + + try { + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + switch (ch) { + case '"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + case '\b': + sb.append("\\b"); + break; + case '\f': + sb.append("\\f"); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + case '/': + sb.append("\\/"); + break; + default: + if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') + || (ch >= '\u2000' && ch <= '\u20FF')) { + + String hs = Integer.toHexString(ch); + sb.append("\\u"); + for (int j = 0; j < 4 - hs.length(); j++) { + sb.append('0'); + } + sb.append(hs.toUpperCase()); + } else { + sb.append(ch); + } + } + } + } catch (IOException e) { + throw new TextException(e); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ImmutableCompoundTypeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/type/ImmutableCompoundTypeBuilder.java new file mode 100644 index 000000000..06556fbab --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ImmutableCompoundTypeBuilder.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.util.HashMap; +import java.util.Map; + +/** + * Used to build Immutable Compound Value objects. + *

    + * The individual values are collected for a given type and when they have all + * been collected then the immutable compound value object is created and + * returned. + *

    + * + * @author rbygrave + * + */ +public final class ImmutableCompoundTypeBuilder { + + private static ThreadLocal local = new ThreadLocal() { + protected synchronized ImmutableCompoundTypeBuilder initialValue() { + return new ImmutableCompoundTypeBuilder(); + } + }; + + private Map, Entry> entryMap = new HashMap, Entry>(); + + /** + * Clear the cache of partial compound objects. + */ + public static void clear() { + local.get().entryMap.clear(); + } + + /** + * Set the value for the property of a compound type. + *

    + * If this is the last value required for the compound type then the + * compound type is created and returned, otherwise null is returned (and we + * need more values set). + *

    + */ + public static Object set(CtCompoundType ct, String propName, Object value) { + return local.get().setValue(ct, propName, value); + } + + private Object setValue(CtCompoundType ct, String propName, Object value) { + + Entry e = getEntry(ct); + Object compoundValue = e.set(propName, value); + if (compoundValue != null) { + removeEntry(ct); + } + return compoundValue; + } + + /** + * Once we have built a compound value we remove the entry. + */ + private void removeEntry(CtCompoundType ct) { + entryMap.remove(ct.getCompoundTypeClass()); + } + + /** + * Get the Entry which contains the values collected so far for this type. + */ + private Entry getEntry(CtCompoundType ct) { + Entry e = entryMap.get(ct.getCompoundTypeClass()); + if (e == null) { + e = new Entry(ct); + entryMap.put(ct.getCompoundTypeClass(), e); + } + return e; + } + + /** + * Holds the values collected so far for a given compound type. + */ + private static class Entry { + + private final CtCompoundType ct; + + private final Map valueMap; + + private Entry(CtCompoundType ct) { + this.ct = ct; + this.valueMap = new HashMap(); + } + + private Object set(String propName, Object value) { + // collect the values... + valueMap.put(propName, value); + + // when got all the values this returns the + // compound value, otherwise null + return ct.create(valueMap); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/LongToTimestampConverter.java b/src/main/java/com/avaje/ebeaninternal/server/type/LongToTimestampConverter.java new file mode 100644 index 000000000..4cf3cc385 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/LongToTimestampConverter.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Timestamp; + +import com.avaje.ebean.config.ScalarTypeConverter; + +public class LongToTimestampConverter implements ScalarTypeConverter{ + + public Long getNullValue() { + return null; + } + + public Timestamp unwrapValue(Long beanType) { + + return new Timestamp(beanType.longValue()); + } + + public Long wrapValue(Timestamp scalarType) { + + return scalarType.getTime(); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java new file mode 100644 index 000000000..87faa97ea --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReader.java @@ -0,0 +1,262 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; + +import com.avaje.ebeaninternal.server.core.Message; + +public class RsetDataReader implements DataReader { + + private static final int bufferSize = 512; + + static final int clobBufferSize = 512; + + static final int stringInitialSize = 512; + + private final ResultSet rset; + + protected int pos; + + public RsetDataReader(ResultSet rset) { + this.rset = rset; + } + + public void close() throws SQLException { + rset.close(); + } + + public boolean next() throws SQLException { + return rset.next(); + } + + public void resetColumnPosition() { + pos = 0; + } + + public void incrementPos(int increment){ + pos += increment; + } + + protected int pos() { + return ++pos; + } + + public Array getArray() throws SQLException { + return rset.getArray(pos()); + } + + + public InputStream getAsciiStream() throws SQLException { + return rset.getAsciiStream(pos()); + } + + public Object getObject() throws SQLException { + return rset.getObject(pos()); + } + + public BigDecimal getBigDecimal() throws SQLException { + return rset.getBigDecimal(pos()); + } + + + public InputStream getBinaryStream() throws SQLException { + return rset.getBinaryStream(pos()); + } + + public Boolean getBoolean() throws SQLException { + boolean v = rset.getBoolean(pos()); + if (rset.wasNull()){ + return null; + } + return Boolean.valueOf(v); + } + + public Byte getByte() throws SQLException { + byte v = rset.getByte(pos()); + if (rset.wasNull()){ + return null; + } + return Byte.valueOf(v); + } + + public byte[] getBytes() throws SQLException { + return rset.getBytes(pos()); + } + + public Date getDate() throws SQLException { + return rset.getDate(pos()); + } + + public Double getDouble() throws SQLException { + double v = rset.getDouble(pos()); + if (rset.wasNull()){ + return null; + } + return Double.valueOf(v); + } + + public Float getFloat() throws SQLException { + float v = rset.getFloat(pos()); + if (rset.wasNull()){ + return null; + } + return Float.valueOf(v); + } + + public Integer getInt() throws SQLException { + int v = rset.getInt(pos()); + if (rset.wasNull()){ + return null; + } + return Integer.valueOf(v); + } + + + public Long getLong() throws SQLException { + long v = rset.getLong(pos()); + if (rset.wasNull()){ + return null; + } + return Long.valueOf(v); + } + + + public Ref getRef() throws SQLException { + return rset.getRef(pos()); + } + + + public Short getShort() throws SQLException { + short s = rset.getShort(pos()); + if (rset.wasNull()){ + return null; + } + return Short.valueOf(s); + } + + + public String getString() throws SQLException { + return rset.getString(pos()); + } + + + public Time getTime() throws SQLException { + return rset.getTime(pos()); + } + + + public Timestamp getTimestamp() throws SQLException { + return rset.getTimestamp(pos()); + } + + public String getStringFromStream() throws SQLException { + Reader reader = rset.getCharacterStream(pos()); + if (reader == null) { + return null; + } + return readStringLob(reader); + } + + public String getStringClob() throws SQLException { + + Clob clob = rset.getClob(pos()); + if (clob == null) { + return null; + } + Reader reader = clob.getCharacterStream(); + if (reader == null) { + return null; + } + return readStringLob(reader); + } + + protected String readStringLob(Reader reader) throws SQLException { + + char[] buffer = new char[clobBufferSize]; + int readLength = 0; + StringBuilder out = new StringBuilder(stringInitialSize); + try { + while ((readLength = reader.read(buffer)) != -1) { + out.append(buffer, 0, readLength); + } + reader.close(); + } catch (IOException e) { + throw new SQLException(Message.msg("persist.clob.io", e.getMessage())); + } + + return out.toString(); + } + + public byte[] getBinaryBytes() throws SQLException { + InputStream in = rset.getBinaryStream(pos()); + return getBinaryLob(in); + } + + public byte[] getBlobBytes() throws SQLException { + Blob blob = rset.getBlob(pos()); + if (blob == null) { + return null; + } + InputStream in = blob.getBinaryStream(); + return getBinaryLob(in); + } + + protected byte[] getBinaryLob(InputStream in) throws SQLException { + + try { + if (in == null) { + return null; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + byte[] buf = new byte[bufferSize]; + int len; + while ((len = in.read(buf, 0, buf.length)) != -1) { + out.write(buf, 0, len); + } + byte[] data = out.toByteArray(); + + if (data.length == 0) { + data = null; + } + in.close(); + out.close(); + return data; + + } catch (IOException e) { + throw new SQLException(e.getClass().getName() + ":" + e.getMessage()); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java new file mode 100644 index 000000000..a61768c55 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/RsetDataReaderIndexed.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.ResultSet; + +public class RsetDataReaderIndexed extends RsetDataReader { + + private final int[] rsetIndexPositions; + + public RsetDataReaderIndexed(ResultSet rset, int[] rsetIndexPositions, boolean rowNumberIncluded) { + super(rset); + if (!rowNumberIncluded){ + this.rsetIndexPositions = rsetIndexPositions; + } else { + this.rsetIndexPositions = new int[rsetIndexPositions.length+1]; + for (int i = 0; i < rsetIndexPositions.length; i++) { + // increment all the column indexes by 1 + this.rsetIndexPositions[i+1] = rsetIndexPositions[i]+1; + } + } + } + + @Override + protected int pos() { + int i = pos++; + return rsetIndexPositions[i]; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalaOptionTypeConverter.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalaOptionTypeConverter.java new file mode 100644 index 000000000..09146b6eb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalaOptionTypeConverter.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import scala.Option; + +import com.avaje.ebean.config.ScalarTypeConverter; + +/** + * A type converter to support scala.Option. + * + * @author rbygrave + * + * @param the underlying type + */ +public class ScalaOptionTypeConverter implements ScalarTypeConverter, S>{ + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Option getNullValue() { + return (scala.Option)scala.None$.MODULE$; + } + + public S unwrapValue(Option beanType) { + + if (beanType.isEmpty()){ + return null; + } else { + return beanType.get(); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Option wrapValue(S scalarType) { + if (scalarType == null){ + return (scala.Option)scala.None$.MODULE$; + } + if (scalarType instanceof scala.Some){ + return (Option)scalarType; + } + return new scala.Some(scalarType); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarDataReader.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarDataReader.java new file mode 100644 index 000000000..f60d1eed2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarDataReader.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; + +/** + * Reads from and binds to database columns. + * + * @author rbygrave + */ +public interface ScalarDataReader { + + /** + * Read and return the appropriate value from the dataReader. + */ + public T read(DataReader dataReader) throws SQLException; + + /** + * Ignore typically by moving the index position. + */ + public void loadIgnore(DataReader dataReader); + + /** + * Bind the value to the underlying preparedStatement. + */ + public void bind(DataBind b, T value) throws SQLException; + + /** + * Accumulate all the scalar types used by an immutable compound value type. + */ + public void accumulateScalarTypes(String propName, CtCompoundTypeScalarList list); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java new file mode 100644 index 000000000..c9986501d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java @@ -0,0 +1,204 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; + +import com.avaje.ebean.text.StringFormatter; +import com.avaje.ebean.text.StringParser; +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * Describes a scalar type. + *

    + * Scalar in the sense that the types are not compound types. Scalar types only + * map to a single database column. + *

    + *

    + * These types fall into two categories. Types that are mapped natively to JDBC + * types and the rest. Types that map to native JDBC types do not require any + * data type conversion to be persisted to the database. These are java types + * that map via java.sql.Types. + *

    + *

    + * Types that are not native to JDBC require some conversion. These include some + * common java types such as java.util.Date, java.util.Calendar, + * java.math.BigInteger. + *

    + *

    + * Note that Booleans may be native for some databases and require conversion on + * other databases. + *

    + */ +public interface ScalarType extends StringParser, StringFormatter, ScalarDataReader { + + /** + * Return the default DB column length for this type. + *

    + * If a BeanProperty has no explicit length defined then this length should + * be assigned. + *

    + *

    + * This is primarily to support defining a length on Enum types (to + * supplement defining the length on the BeanProperty directly). + *

    + */ + public int getLength(); + + /** + * Return true if the type is native to JDBC. + *

    + * If it is native to JDBC then its values/instances do not need to be + * converted to and from an associated JDBC type. + *

    + */ + public boolean isJdbcNative(); + + /** + * Return the type as per java.sql.Types that this maps to. + *

    + * This type should be consistent with the toJdbcType() method in converting + * the type to the appropriate type for binding to preparedStatements. + *

    + */ + public int getJdbcType(); + + /** + * Return the type that matches the bean property type. + *

    + * This represents the 'logical' type rather than the JDBC type this maps + * to. + *

    + */ + public Class getType(); + + /** + * Read the value from the resultSet and convert if necessary to the logical + * bean property value. + */ + public T read(DataReader dataReader) throws SQLException; + + /** + * Ignore the reading of this value. Typically this means moving the index + * position in the ResultSet. + */ + public void loadIgnore(DataReader dataReader); + + /** + * Convert (if necessary) and bind the value to the preparedStatement. + *

    + * value may need to be converted from the logical bean property type to the + * JDBC type. + *

    + */ + public void bind(DataBind b, T value) throws SQLException; + + /** + * Convert the value as necessary to the JDBC type. + *

    + * Note that this should also match the type as per the getJdbcType() + * method. + *

    + *

    + * This is typically used when the matching type is used in a where clause + * and we use this to ensure it is an appropriate jdbc type. + *

    + */ + public Object toJdbcType(Object value); + + /** + * Convert the value as necessary to the logical Bean type. + *

    + * The type as per the bean property. + *

    + *

    + * This is used to automatically convert id values (typically from a string + * to a int, long or UUID). + *

    + */ + public T toBeanType(Object value); + + /** + * Convert the type into a string representation. + *

    + * Reciprocal of parse(). + *

    + */ + public String formatValue(T v); + + /** + * Convert the type into a string representation. + *

    + * This assumes the value is of the correct type. + *

    + *

    + * This is so that ScalarType also implements the StringFormatter interface. + *

    + */ + public String format(Object v); + + /** + * Convert the string value to the appropriate java object. + *

    + * Mostly used to support CSV, JSON and XML parsing. + *

    + *

    + * Reciprocal of formatValue(). + *

    + */ + public T parse(String value); + + /** + * Convert the systemTimeMillis into the appropriate java object. + *

    + * For non dateTime types this will throw an exception. + *

    + */ + public T parseDateTime(long dateTime); + + /** + * Return true if the type can accept long systemTimeMillis input. + *

    + * This is used to determine if is is sensible to use the + * {@link #parseDateTime(long)} method. + *

    + *

    + * This includes the Date, Calendar, sql Date, Time, Timestamp, JODA types + * as well as Long, BigDecimal and String (although it generally is not + * expected to parse systemTimeMillis to a String or BigDecimal). + *

    + */ + public boolean isDateTimeCapable(); + + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx); + + public String jsonToString(T value, JsonValueAdapter ctx); + + public T jsonFromString(String value, JsonValueAdapter ctx); + + public Object readData(DataInput dataInput) throws IOException; + + public void writeData(DataOutput dataOutput, Object v) throws IOException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java new file mode 100644 index 000000000..a74940667 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + + + +/** + * Base ScalarType object. + */ +public abstract class ScalarTypeBase implements ScalarType { + + protected final Class type; + protected final boolean jdbcNative; + protected final int jdbcType; + + public ScalarTypeBase(Class type, boolean jdbcNative, int jdbcType) { + this.type = type; + this.jdbcNative = jdbcNative; + this.jdbcType = jdbcType; + } + + /** + * Just return 0. + */ + public int getLength() { + return 0; + } + + public boolean isJdbcNative() { + return jdbcNative; + } + + public int getJdbcType() { + return jdbcType; + } + + public Class getType() { + return type; + } + + @SuppressWarnings("unchecked") + public String format(Object v) { + return formatValue((T)v); + } + + /** + * Return true if the value is null. + */ + public boolean isDbNull(Object value) { + return value == null; + } + + /** + * Returns the value that was passed in. + */ + public Object getDbNullValue(Object value) { + return value; + } + + public void loadIgnore(DataReader dataReader) { + dataReader.incrementPos(1); + } + + public void accumulateScalarTypes(String propName, CtCompoundTypeScalarList list) { + list.addScalarType(propName, this); + } + + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx) { + String v = jsonToString(value, ctx); + buffer.append(v); + } + + public String jsonToString(T value, JsonValueAdapter ctx) { + return formatValue(value); + } + + public T jsonFromString(String value, JsonValueAdapter ctx) { + return parse(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java new file mode 100644 index 000000000..b936299ed --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * Base class for Date types. + */ +public abstract class ScalarTypeBaseDate extends ScalarTypeBase { + + public ScalarTypeBaseDate(Class type, boolean jdbcNative, int jdbcType) { + super(type, jdbcNative, jdbcType); + } + + public abstract java.sql.Date convertToDate(T t); + + public abstract T convertFromDate(java.sql.Date ts); + + public void bind(DataBind b, T value) throws SQLException { + if (value == null){ + b.setNull(Types.DATE); + } else { + Date date = convertToDate(value); + b.setDate(date); + } + } + + public T read(DataReader dataReader) throws SQLException { + + Date ts = dataReader.getDate(); + if (ts == null){ + return null; + } else { + return convertFromDate(ts); + } + } + + public String formatValue(T t) { + Date date = convertToDate(t); + return date.toString(); + } + + public T parse(String value) { + Date date = Date.valueOf(value); + return convertFromDate(date); + } + + public T parseDateTime(long systemTimeMillis) { + Date ts = new Date(systemTimeMillis); + return convertFromDate(ts); + } + + public boolean isDateTimeCapable() { + return true; + } + + @Override + public String jsonToString(T value, JsonValueAdapter ctx) { + Date date = convertToDate(value); + return ctx.jsonFromDate(date); + } + + @Override + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx) { + String s = jsonToString(value, ctx); + buffer.append(s); + } + + @Override + public T jsonFromString(String value, JsonValueAdapter ctx) { + Date ts = ctx.jsonToDate(value); + return convertFromDate(ts); + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + long val = dataInput.readLong(); + Date date = new Date(val); + return convertFromDate(date); + } + } + + @SuppressWarnings("unchecked") + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + T value = (T)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + Date date = convertToDate(value); + dataOutput.writeLong(date.getTime()); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java new file mode 100644 index 000000000..1aebd6098 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * Base type for DateTime types. + */ +public abstract class ScalarTypeBaseDateTime extends ScalarTypeBase { + + public ScalarTypeBaseDateTime(Class type, boolean jdbcNative, int jdbcType) { + super(type, jdbcNative, jdbcType); + } + + public abstract Timestamp convertToTimestamp(T t); + + public abstract T convertFromTimestamp(Timestamp ts); + + public void bind(DataBind b, T value) throws SQLException { + if (value == null){ + b.setNull(Types.TIMESTAMP); + } else { + Timestamp ts = convertToTimestamp(value); + b.setTimestamp(ts); + } + } + + public T read(DataReader dataReader) throws SQLException { + + Timestamp ts = dataReader.getTimestamp(); + if (ts == null){ + return null; + } else { + return convertFromTimestamp(ts); + } + } + + public String formatValue(T t) { + Timestamp ts = convertToTimestamp(t); + return ts.toString(); + } + + public T parse(String value) { + Timestamp ts = Timestamp.valueOf(value); + return convertFromTimestamp(ts); + } + + public T parseDateTime(long systemTimeMillis) { + Timestamp ts = new Timestamp(systemTimeMillis); + return convertFromTimestamp(ts); + } + + public boolean isDateTimeCapable() { + return true; + } + + @Override + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx) { + String v = jsonToString(value, ctx); + buffer.append(v); + } + + @Override + public String jsonToString(T value, JsonValueAdapter ctx) { + Timestamp ts = convertToTimestamp(value); + return ctx.jsonFromTimestamp(ts); + } + + @Override + public T jsonFromString(String value, JsonValueAdapter ctx) { + Timestamp ts = ctx.jsonToTimestamp(value); + return convertFromTimestamp(ts); + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + long val = dataInput.readLong(); + Timestamp ts = new Timestamp(val); + return convertFromTimestamp(ts); + } + } + + @SuppressWarnings("unchecked") + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + T value = (T)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + Timestamp ts = convertToTimestamp(value); + dataOutput.writeLong(ts.getTime()); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java new file mode 100644 index 000000000..c0935b847 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java @@ -0,0 +1,139 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * Base ScalarType for types which converts to and from a VARCHAR database column. + */ +public abstract class ScalarTypeBaseVarchar extends ScalarTypeBase { + + public ScalarTypeBaseVarchar(Class type) { + super(type, false, Types.VARCHAR); + } + + public ScalarTypeBaseVarchar(Class type, boolean jdbcNative, int jdbcType) { + super(type, jdbcNative, jdbcType); + } + + public abstract String formatValue(T v); + + public abstract T parse(String value); + + public abstract T convertFromDbString(String dbValue); + + public abstract String convertToDbString(T beanValue); + + public void bind(DataBind b, T value) throws SQLException { + if (value == null){ + b.setNull(Types.VARCHAR); + + } else { + String s = convertToDbString(value); + b.setString(s); + } + } + + public T read(DataReader dataReader) throws SQLException { + String s = dataReader.getString(); + if (s == null){ + return null; + } else { + return convertFromDbString(s); + } + } + + @SuppressWarnings("unchecked") + public T toBeanType(Object value) { + if (value == null){ + return null; + } + if (value instanceof String){ + return parse((String)value); + } + return (T)value; + } + + public Object toJdbcType(Object value){ + if (value instanceof String){ + return parse((String)value); + } + return value; + } + + public T parseDateTime(long systemTimeMillis) { + throw new TextException("Not Supported"); + } + + public boolean isDateTimeCapable() { + return false; + } + + @SuppressWarnings("unchecked") + public String format(Object v) { + return formatValue((T) v); + } + + public T jsonFromString(String value, JsonValueAdapter ctx) { + return parse(value); + } + + @Override + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx) { + String s = format(value); + EscapeJson.escapeQuote(s, buffer); + } + + public String toJsonString(Object value, JsonValueAdapter ctx) { + String s = format(value); + return EscapeJson.escapeQuote(s); + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + String val = dataInput.readUTF(); + return convertFromDbString(val); + } + } + + @SuppressWarnings("unchecked") + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + T value = (T)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + String s = convertToDbString(value); + dataOutput.writeUTF(s); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java new file mode 100644 index 000000000..8b01b43f6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.math.BigDecimal; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for BigDecimal. + */ +public class ScalarTypeBigDecimal extends ScalarTypeBase { + + public ScalarTypeBigDecimal() { + super(BigDecimal.class, true, Types.DECIMAL); + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + double val = dataInput.readDouble(); + return new BigDecimal(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + BigDecimal b = (BigDecimal)v; + if (b == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeDouble(b.doubleValue()); + } + } + + public void bind(DataBind b, BigDecimal value) throws SQLException { + if (value == null){ + b.setNull(Types.DECIMAL); + } else { + b.setBigDecimal(value); + } + } + + public BigDecimal read(DataReader dataReader) throws SQLException { + + return dataReader.getBigDecimal(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toBigDecimal(value); + } + + public BigDecimal toBeanType(Object value) { + return BasicTypeConverter.toBigDecimal(value); + } + + public String formatValue(BigDecimal t) { + return t.toPlainString(); + } + + public BigDecimal parse(String value) { + return new BigDecimal(value); + } + + public BigDecimal parseDateTime(long systemTimeMillis) { + return BigDecimal.valueOf(systemTimeMillis); + } + + public boolean isDateTimeCapable() { + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java new file mode 100644 index 000000000..7ffd809ac --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java @@ -0,0 +1,310 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Boolean and boolean. + *

    + * This may or may not be a native jdbc type depending on the database and jdbc + * driver. + *

    + */ +public class ScalarTypeBoolean { + + public static class Native extends BooleanBase { + + /** + * Native Boolean database type. + */ + public Native() { + super(true, Types.BOOLEAN); + } + + public Boolean toBeanType(Object value) { + return BasicTypeConverter.toBoolean(value); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.convert(value, jdbcType); + } + + public void bind(DataBind b, Boolean value) throws SQLException { + if (value == null) { + b.setNull(Types.BOOLEAN); + } else { + b.setBoolean(value); + } + + } + + public Boolean read(DataReader dataReader) throws SQLException { + return dataReader.getBoolean(); + } + } + + /** + * The Class BitBoolean converts a JDBC type BIT to a java boolean + * + *

    + * Sometimes booleans may be mapped to the JDBC type BIT. To use + * the BitBoolean specify type.boolean.dbtype="bit" in the ebean configuration + *

    + */ + public static class BitBoolean extends BooleanBase { + + /** + * Native Boolean database type. + */ + public BitBoolean() { + super(true, Types.BIT); + } + + public Boolean toBeanType(Object value) { + return BasicTypeConverter.toBoolean(value); + } + + public Object toJdbcType(Object value) { + // use JDBC driver to convert boolean to bit + return BasicTypeConverter.toBoolean(value); + } + + public void bind(DataBind b, Boolean value) throws SQLException { + if (value == null) { + b.setNull(Types.BIT); + } else { + // use JDBC driver to convert boolean to bit + b.setBoolean(value); + } + } + + public Boolean read(DataReader dataReader) throws SQLException { + return dataReader.getBoolean(); + } + + } + + /** + * Converted to/from an Integer in the Database. + */ + public static class IntBoolean extends BooleanBase { + + private final Integer trueValue; + private final Integer falseValue; + + public IntBoolean(Integer trueValue, Integer falseValue) { + super(false, Types.INTEGER); + this.trueValue = trueValue; + this.falseValue = falseValue; + } + + @Override + public int getLength() { + return 1; + } + + public void bind(DataBind b, Boolean value) throws SQLException { + if (value == null) { + b.setNull(Types.INTEGER); + } else { + b.setInt(toInteger(value)); + } + } + + public Boolean read(DataReader dataReader) throws SQLException { + Integer i = dataReader.getInt(); + if (i == null){ + return null; + } + if (i.equals(trueValue)){ + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + public Object toJdbcType(Object value) { + return toInteger(value); + } + + /** + * Convert the Boolean value to the db value. + */ + public Integer toInteger(Object value) { + if (value == null) { + return null; + } + Boolean b = (Boolean) value; + if (b.booleanValue()) { + return trueValue; + } else { + return falseValue; + } + } + + /** + * Convert the db value to the Boolean value. + */ + public Boolean toBeanType(Object value) { + if (value == null) { + return null; + } + if (value instanceof Boolean){ + return (Boolean)value; + } + if (trueValue.equals(value)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + } + + /** + * Converted to/from an Integer in the Database. + */ + public static class StringBoolean extends BooleanBase { + + private final String trueValue; + private final String falseValue; + + public StringBoolean(String trueValue, String falseValue) { + super(false, Types.VARCHAR); + this.trueValue = trueValue; + this.falseValue = falseValue; + } + + @Override + public int getLength() { + // typically this will return 1 + return Math.max(trueValue.length(), falseValue.length()); + } + + public void bind(DataBind b, Boolean value) throws SQLException { + if (value == null) { + b.setNull(Types.VARCHAR); + } else { + b.setString(toString(value)); + } + } + + public Boolean read(DataReader dataReader) throws SQLException { + String string = dataReader.getString(); + if (string == null) { + return null; + } + + if (string.equals(trueValue)){ + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + public Object toJdbcType(Object value) { + return toString(value); + } + + /** + * Convert the Boolean value to the db value. + */ + public String toString(Object value) { + if (value == null) { + return null; + } + Boolean b = (Boolean) value; + if (b.booleanValue()) { + return trueValue; + } else { + return falseValue; + } + } + + /** + * Convert the db value to the Boolean value. + */ + public Boolean toBeanType(Object value) { + if (value == null) { + return null; + } + if (value instanceof Boolean){ + return (Boolean)value; + } + if (trueValue.equals(value)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + } + + public static abstract class BooleanBase extends ScalarTypeBase { + + public BooleanBase(boolean jdbcNative, int jdbcType) { + super(Boolean.class, jdbcNative, jdbcType); + } + + public String formatValue(Boolean t) { + return t.toString(); + } + + public Boolean parse(String value) { + return Boolean.valueOf(value); + } + + public Boolean parseDateTime(long systemTimeMillis) { + throw new TextException("Not Supported"); + } + + public boolean isDateTimeCapable() { + return false; + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + boolean val = dataInput.readBoolean(); + return Boolean.valueOf(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Boolean val = (Boolean) v; + if (val == null) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeBoolean(val.booleanValue()); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java new file mode 100644 index 000000000..5021c0822 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Byte. + */ +public class ScalarTypeByte extends ScalarTypeBase { + + public ScalarTypeByte() { + super(Byte.class, true,Types.TINYINT ); + } + + public void bind(DataBind b, Byte value) throws SQLException { + if (value == null){ + b.setNull(Types.TINYINT); + } else { + b.setByte(value); + } + } + + public Byte read(DataReader dataReader) throws SQLException { + return dataReader.getByte(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toByte(value); + } + + public Byte toBeanType(Object value) { + return BasicTypeConverter.toByte(value); + } + + + public String formatValue(Byte t) { + return t.toString(); + } + + public Byte parse(String value) { + throw new TextException("Not supported"); + } + + public Byte parseDateTime(long systemTimeMillis) { + throw new TextException("Not Supported"); + } + + public boolean isDateTimeCapable() { + return false; + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + byte val = dataInput.readByte(); + return Byte.valueOf(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Byte val = (Byte) v; + if (val == null) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeByte(val); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java new file mode 100644 index 000000000..e0944412b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; + +import com.avaje.ebean.text.TextException; + +/** + * Base type for binary types. + */ +public abstract class ScalarTypeBytesBase extends ScalarTypeBase { + + protected ScalarTypeBytesBase(boolean jdbcNative, int jdbcType) { + super(byte[].class, jdbcNative, jdbcType); + } + + public Object convertFromBytes(byte[] bytes) { + return bytes; + } + + public byte[] convertToBytes(Object value) { + return (byte[]) value; + } + + public void bind(DataBind b, byte[] value) throws SQLException { + if (value == null) { + b.setNull(jdbcType); + } else { + b.setBytes(value); + } + } + + public Object toJdbcType(Object value) { + return value; + } + + public byte[] toBeanType(Object value) { + return (byte[])value; + } + + + public String formatValue(byte[] t) { + throw new TextException("Not supported"); + } + + public byte[] parse(String value) { + throw new TextException("Not supported"); + } + + public byte[] parseDateTime(long systemTimeMillis) { + throw new TextException("Not supported"); + } + + public boolean isDateTimeCapable() { + return false; + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + int len = dataInput.readInt(); + byte[] buf = new byte[len]; + dataInput.readFully(buf, 0, buf.length); + return buf; + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + if (v == null){ + dataOutput.writeBoolean(false); + } else { + byte[] bytes = convertToBytes(v); + dataOutput.writeInt(bytes.length); + dataOutput.write(bytes); + } + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBinary.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBinary.java new file mode 100644 index 000000000..bb43ed412 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBinary.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for Types.BINARY to byte[]. + */ +public class ScalarTypeBytesBinary extends ScalarTypeBytesBase { + + public ScalarTypeBytesBinary() { + super(true, Types.BINARY); + } + + public byte[] read(DataReader dataReader) throws SQLException { + return dataReader.getBytes(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBlob.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBlob.java new file mode 100644 index 000000000..b72d7230a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBlob.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for BLOB. + */ +public class ScalarTypeBytesBlob extends ScalarTypeBytesBase { + + public ScalarTypeBytesBlob() { + super(true, Types.BLOB); + } + + public byte[] read(DataReader dataReader) throws SQLException { + + return dataReader.getBlobBytes(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java new file mode 100644 index 000000000..251b2a5e5 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * Encrypted ScalarType that wraps a byte[] types. + * + * @author rbygrave + * + */ +public class ScalarTypeBytesEncrypted implements ScalarType { + + private final ScalarTypeBytesBase baseType; + + private final DataEncryptSupport dataEncryptSupport; + + + public ScalarTypeBytesEncrypted(ScalarTypeBytesBase baseType, DataEncryptSupport dataEncryptSupport) { + this.baseType = baseType; + this.dataEncryptSupport = dataEncryptSupport; + } + + public void bind(DataBind b, byte[] value) throws SQLException { + value = dataEncryptSupport.encrypt(value); + baseType.bind(b, value); + } + + public int getJdbcType() { + return baseType.getJdbcType(); + } + + public int getLength() { + return baseType.getLength(); + } + + public Class getType() { + return byte[].class; + } + + public boolean isDateTimeCapable() { + return baseType.isDateTimeCapable(); + } + + public boolean isJdbcNative() { + return baseType.isJdbcNative(); + } + + public void loadIgnore(DataReader dataReader) { + baseType.loadIgnore(dataReader); + } + + public String format(Object v) { + throw new RuntimeException("Not used"); + } + + public String formatValue(byte[] v) { + throw new RuntimeException("Not used"); + } + + public byte[] parse(String value) { + return baseType.parse(value); + } + + public byte[] parseDateTime(long systemTimeMillis) { + return baseType.parseDateTime(systemTimeMillis); + } + + public byte[] read(DataReader dataReader) throws SQLException { + + byte[] data = baseType.read(dataReader); + data = dataEncryptSupport.decrypt(data); + return data; + } + + public byte[] toBeanType(Object value) { + return baseType.toBeanType(value); + } + + public Object toJdbcType(Object value) { + return baseType.toJdbcType(value); + } + + public void accumulateScalarTypes(String propName, CtCompoundTypeScalarList list) { + baseType.accumulateScalarTypes(propName, list); + } + + public void jsonWrite(WriteJsonBuffer buffer, byte[] value, JsonValueAdapter ctx) { + baseType.jsonWrite(buffer, value, ctx); + } + + public String jsonToString(byte[] value, JsonValueAdapter ctx) { + return baseType.jsonToString(value, ctx); + } + + public byte[] jsonFromString(String value, JsonValueAdapter ctx) { + return baseType.jsonFromString(value, ctx); + } + + public Object readData(DataInput dataInput) throws IOException { + int len = dataInput.readInt(); + byte[] value = new byte[len]; + dataInput.readFully(value); + return value; + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + byte[] value = (byte[])v; + dataOutput.writeInt(value.length); + dataOutput.write(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesLongVarbinary.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesLongVarbinary.java new file mode 100644 index 000000000..b5f51452c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesLongVarbinary.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for Longvarbinary. + */ +public class ScalarTypeBytesLongVarbinary extends ScalarTypeBytesBase { + + public ScalarTypeBytesLongVarbinary() { + super(true, Types.LONGVARBINARY); + } + + public byte[] read(DataReader dataReader) throws SQLException { + return dataReader.getBinaryBytes(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesVarbinary.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesVarbinary.java new file mode 100644 index 000000000..80645d2c6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesVarbinary.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for Types.VARBINARY to byte[]. + */ +public class ScalarTypeBytesVarbinary extends ScalarTypeBytesBase { + + public ScalarTypeBytesVarbinary() { + super(true, Types.VARBINARY); + } + + public byte[] read(DataReader dataReader) throws SQLException { + return dataReader.getBytes(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCalendar.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCalendar.java new file mode 100644 index 000000000..ee229a5d3 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCalendar.java @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for java.util.Calendar. + */ +public class ScalarTypeCalendar extends ScalarTypeBaseDateTime { + + public ScalarTypeCalendar(int jdbcType) { + super(Calendar.class, false, jdbcType); + } + + public void bind(DataBind b, Calendar value) throws SQLException { + if (value == null){ + b.setNull(Types.TIMESTAMP); + } else { + Calendar date = (Calendar)value; + if (jdbcType == Types.TIMESTAMP){ + Timestamp timestamp = new Timestamp(date.getTimeInMillis()); + b.setTimestamp(timestamp); + } else { + Date d = new Date(date.getTimeInMillis()); + b.setDate(d); + } + } + } + + @Override + public Calendar convertFromTimestamp(Timestamp ts) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(ts.getTime()); + return calendar; + } + + @Override + public Timestamp convertToTimestamp(Calendar t) { + return new Timestamp(t.getTimeInMillis()); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.convert(value, jdbcType); + } + + public Calendar toBeanType(Object value) { + return BasicTypeConverter.toCalendar(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeChar.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeChar.java new file mode 100644 index 000000000..b74e69b3f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeChar.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for char. + */ +public class ScalarTypeChar extends ScalarTypeBaseVarchar { + + public ScalarTypeChar() { + super(char.class, false, Types.VARCHAR); + } + + @Override + public Character convertFromDbString(String dbValue) { + return dbValue.charAt(0); + } + + @Override + public String convertToDbString(Character beanValue) { + return beanValue.toString(); + } + + public void bind(DataBind b, Character value) throws SQLException { + if (value == null){ + b.setNull(Types.VARCHAR); + } else { + String s = BasicTypeConverter.toString(value); + b.setString(s); + } + } + + public Character read(DataReader dataReader) throws SQLException { + String string = dataReader.getString(); + if (string == null || string.length()==0){ + return null; + } else { + return string.charAt(0); + } + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toString(value); + } + + public Character toBeanType(Object value) { + String s = BasicTypeConverter.toString(value); + return s.charAt(0); + } + + public String formatValue(Character t) { + return t.toString(); + } + + public Character parse(String value) { + return value.charAt(0); + } + + @Override + public Character jsonFromString(String value, JsonValueAdapter ctx) { + return value.charAt(0); + } + + @Override + public String jsonToString(Character value, JsonValueAdapter ctx) { + return EscapeJson.escapeQuote(value.toString()); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCharArray.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCharArray.java new file mode 100644 index 000000000..4d96b89e0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCharArray.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for char[]. + */ +public class ScalarTypeCharArray extends ScalarTypeBaseVarchar{ + + public ScalarTypeCharArray() { + super(char[].class, false, Types.VARCHAR); + } + + @Override + public char[] convertFromDbString(String dbValue) { + return dbValue.toCharArray(); + } + + @Override + public String convertToDbString(char[] beanValue) { + return new String(beanValue); + } + + public void bind(DataBind b, char[] value) throws SQLException { + if (value == null){ + b.setNull(Types.VARCHAR); + } else { + String s = BasicTypeConverter.toString(value); + b.setString(s); + } + } + + public char[] read(DataReader dataReader) throws SQLException { + String string = dataReader.getString(); + if (string == null){ + return null; + } else { + return string.toCharArray(); + } + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toString(value); + } + + public char[] toBeanType(Object value) { + String s = BasicTypeConverter.toString(value); + return s.toCharArray(); + } + + public String formatValue(char[] t) { + return String.valueOf(t); + } + + public char[] parse(String value) { + return value.toCharArray(); + } + + @Override + public char[] jsonFromString(String value, JsonValueAdapter ctx) { + return value.toCharArray(); + } + + @Override + public String jsonToString(char[] value, JsonValueAdapter ctx) { + return EscapeJson.escapeQuote(String.valueOf(value)); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClass.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClass.java new file mode 100644 index 000000000..f8e55979b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClass.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2010 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import javax.persistence.PersistenceException; + +/** + * ScalarType for Class that persists it to VARCHAR column. + * + * @author emcgreal + * @author rbygrave + */ +@SuppressWarnings({ "rawtypes" }) +public class ScalarTypeClass extends ScalarTypeBaseVarchar { + + public ScalarTypeClass() { + super(Class.class); + } + + @Override + public int getLength() { + return 255; + } + + @Override + public Class convertFromDbString(String dbValue) { + return parse(dbValue); + } + + @Override + public String convertToDbString(Class beanValue) { + return beanValue.getCanonicalName(); + } + + public String formatValue(Class v) { + return v.getCanonicalName(); + } + + public Class parse(String value) { + try { + return Class.forName(value); + } catch (Exception e) { + String msg = "Unable to find Class "+value; + throw new PersistenceException(msg, e); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClob.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClob.java new file mode 100644 index 000000000..8fe791d3b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeClob.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for String. + */ +public class ScalarTypeClob extends ScalarTypeBaseVarchar { + + static final int clobBufferSize = 512; + + static final int stringInitialSize = 512; + + protected ScalarTypeClob(boolean jdbcNative, int jdbcType) { + super(String.class, jdbcNative, jdbcType); + } + + public ScalarTypeClob() { + super(String.class, true, Types.CLOB); + } + + @Override + public String convertFromDbString(String dbValue) { + return dbValue; + } + + @Override + public String convertToDbString(String beanValue) { + return beanValue; + } + + public void bind(DataBind b, String value) throws SQLException { + if (value == null) { + b.setNull(Types.VARCHAR); + } else { + b.setString(value); + } + } + + public String read(DataReader dataReader) throws SQLException { + + return dataReader.getStringClob(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toString(value); + } + + public String toBeanType(Object value) { + return BasicTypeConverter.toString(value); + } + + + public String formatValue(String t) { + return t; + } + + public String parse(String value) { + return value; + } + + @Override + public String jsonFromString(String value, JsonValueAdapter ctx) { + return value; + } + + @Override + public String jsonToString(String value, JsonValueAdapter ctx) { + return EscapeJson.escapeQuote(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCurrency.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCurrency.java new file mode 100644 index 000000000..d20eb1c08 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeCurrency.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.util.Currency; + +/** + * ScalarType for java.util.Currency which converts to and from a VARCHAR database column. + */ +public class ScalarTypeCurrency extends ScalarTypeBaseVarchar { + + public ScalarTypeCurrency() { + super(Currency.class); + } + + @Override + public int getLength() { + return 3; + } + + @Override + public Currency convertFromDbString(String dbValue) { + return Currency.getInstance(dbValue); + } + + @Override + public String convertToDbString(Currency beanValue) { + return ((Currency)beanValue).getCurrencyCode(); + } + + public String formatValue(Currency v) { + return v.toString(); + } + + public Currency parse(String value) { + return Currency.getInstance(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDate.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDate.java new file mode 100644 index 000000000..c4c364cab --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDate.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for java.sql.Date. + */ +public class ScalarTypeDate extends ScalarTypeBaseDate { + + public ScalarTypeDate() { + super(Date.class, true, Types.DATE); + } + + @Override + public Date convertFromDate(Date date) { + return date; + } + + @Override + public Date convertToDate(Date t) { + return t; + } + + public void bind(DataBind b, java.sql.Date value) throws SQLException { + if (value == null){ + b.setNull(Types.DATE); + } else { + b.setDate(value); + } + } + + public java.sql.Date read(DataReader dataReader) throws SQLException { + return dataReader.getDate(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toDate(value); + } + + public java.sql.Date toBeanType(Object value) { + return BasicTypeConverter.toDate(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java new file mode 100644 index 000000000..6f59c59cc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Double and double. + */ +public class ScalarTypeDouble extends ScalarTypeBase { + + public ScalarTypeDouble() { + super(Double.class, true, Types.DOUBLE); + } + + public void bind(DataBind b, Double value) throws SQLException { + if (value == null){ + b.setNull(Types.DOUBLE); + } else { + b.setDouble(value.doubleValue()); + } + } + + public Double read(DataReader dataReader) throws SQLException { + + return dataReader.getDouble(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toDouble(value); + } + + public Double toBeanType(Object value) { + return BasicTypeConverter.toDouble(value); + } + + + public String formatValue(Double t) { + return t.toString(); + } + + public Double parse(String value) { + return Double.valueOf(value); + } + + public Double parseDateTime(long systemTimeMillis) { + return Double.valueOf(systemTimeMillis); + } + + public boolean isDateTimeCapable() { + return true; + } + + public String toJsonString(Double value) { + if(value.isInfinite() || value.isNaN()) { + return "null"; + } else { + return value.toString(); + } + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + double val = dataInput.readDouble(); + return Double.valueOf(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Double value = (Double)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeDouble(value.doubleValue()); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java new file mode 100644 index 000000000..845d5fab7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +public class ScalarTypeEncryptedWrapper implements ScalarType { + + private final ScalarType wrapped; + + private final DataEncryptSupport dataEncryptSupport; + + private final ScalarTypeBytesBase byteArrayType; + + public ScalarTypeEncryptedWrapper(ScalarType wrapped, ScalarTypeBytesBase byteArrayType, DataEncryptSupport dataEncryptSupport) { + this.wrapped = wrapped; + this.byteArrayType = byteArrayType; + this.dataEncryptSupport = dataEncryptSupport; + } + + public Object readData(DataInput dataInput) throws IOException { + return wrapped.readData(dataInput); + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + wrapped.writeData(dataOutput, v); + } + + public T read(DataReader dataReader) throws SQLException { + + byte[] data = dataReader.getBytes(); + String formattedValue = dataEncryptSupport.decryptObject(data); + if (formattedValue == null){ + return null; + } + return wrapped.parse(formattedValue); + } + + private byte[] encrypt(T value){ + String formatValue = wrapped.formatValue(value); + return dataEncryptSupport.encryptObject(formatValue); + } + + public void bind(DataBind b, T value) throws SQLException { + + byte[] encryptedValue = encrypt(value); + byteArrayType.bind(b, encryptedValue); + } + + public int getJdbcType() { + return byteArrayType.getJdbcType(); + } + + public int getLength() { + return byteArrayType.getLength(); + } + + public Class getType() { + return wrapped.getType(); + } + + public boolean isDateTimeCapable() { + return wrapped.isDateTimeCapable(); + } + + public boolean isJdbcNative() { + return false; + } + + public void loadIgnore(DataReader dataReader) { + wrapped.loadIgnore(dataReader); + } + + @SuppressWarnings("unchecked") + public String format(Object v) { + return formatValue((T)v); + } + + public String formatValue(T v) { + return wrapped.formatValue(v); + } + + public T parse(String value) { + return wrapped.parse(value); + } + + public T parseDateTime(long systemTimeMillis) { + return wrapped.parseDateTime(systemTimeMillis); + } + + public T toBeanType(Object value) { + return wrapped.toBeanType(value); + } + + public Object toJdbcType(Object value) { + return wrapped.toJdbcType(value); + } + + public void accumulateScalarTypes(String propName, CtCompoundTypeScalarList list) { + wrapped.accumulateScalarTypes(propName, list); + } + + public String jsonToString(T value, JsonValueAdapter ctx) { + return wrapped.jsonToString(value, ctx); + } + + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx) { + wrapped.jsonWrite(buffer, value, ctx); + } + + public T jsonFromString(String value, JsonValueAdapter ctx) { + return wrapped.jsonFromString(value, ctx); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnum.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnum.java new file mode 100644 index 000000000..1e279ee1d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnum.java @@ -0,0 +1,13 @@ +package com.avaje.ebeaninternal.server.type; + +/** + * Marker interface for the Enum scalar types. + */ +public interface ScalarTypeEnum { + + /** + * Return the IN values for DB constraint construction. + */ + public String getContraintInValues(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java new file mode 100644 index 000000000..416ee977a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; +import java.util.EnumSet; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebean.text.json.JsonValueAdapter; + + +/** + * JPA standard based Enum scalar type. + *

    + * Converts between bean Enum types to database string or integer columns. + *

    + *

    + * The limitation of this class is that it converts the Enum to either the ordinal + * or string value of the Enum. If you wish to convert the Enum to some other value + * then you should look at the Ebean specific @EnumMapping. + *

    + */ +public class ScalarTypeEnumStandard { + + @SuppressWarnings({"rawtypes","unchecked"}) + public static class StringEnum extends EnumBase implements ScalarTypeEnum { + + private final int length; + + /** + * Create a ScalarTypeEnum. + */ + public StringEnum(Class enumType) { + super(enumType, false, Types.VARCHAR); + this.length = maxValueLength(enumType); + } + + /** + * Return the IN values for DB constraint construction. + */ + public String getContraintInValues(){ + + StringBuilder sb = new StringBuilder(); + + sb.append("("); + Object[] ea = enumType.getEnumConstants(); + for (int i = 0; i < ea.length; i++) { + Enum e = (Enum)ea[i]; + if (i > 0){ + sb.append(","); + } + sb.append("'").append(e.toString()).append("'"); + } + sb.append(")"); + + return sb.toString(); + } + + private int maxValueLength(Class enumType){ + + int maxLen = 0; + + Object[] ea = enumType.getEnumConstants(); + for (int i = 0; i < ea.length; i++) { + Enum e = (Enum)ea[i]; + maxLen = Math.max(maxLen, e.toString().length()); + } + + return maxLen; + } + + public int getLength() { + return length; + } + + public void bind(DataBind b, Object value) throws SQLException { + if (value == null){ + b.setNull(Types.VARCHAR); + } else { + b.setString(value.toString()); + } + } + + public Object read(DataReader dataReader) throws SQLException { + + String string = dataReader.getString(); + if (string == null){ + return null; + } else { + return Enum.valueOf(enumType, string); + } + } + + /** + * Convert the Boolean value to the db value. + */ + public Object toJdbcType(Object beanValue) { + if (beanValue == null) { + return null; + } + Enum e = (Enum)beanValue; + return e.toString(); + } + + public Object toBeanType(Object dbValue) { + if (dbValue == null) { + return null; + } + + return Enum.valueOf(enumType, (String)dbValue); + } + + } + + @SuppressWarnings({"rawtypes","unchecked"}) + public static class OrdinalEnum extends EnumBase implements ScalarTypeEnum { + + private final Object[] enumArray; + + /** + * Create a ScalarTypeEnum. + */ + public OrdinalEnum(Class enumType) { + super(enumType, false, Types.INTEGER); + this.enumArray = EnumSet.allOf(enumType).toArray(); + } + + /** + * Return the IN values for DB constraint construction. + */ + public String getContraintInValues(){ + + StringBuilder sb = new StringBuilder(); + + sb.append("("); + for (int i = 0; i < enumArray.length; i++) { + Enum e = (Enum)enumArray[i]; + if (i > 0){ + sb.append(","); + } + sb.append(e.ordinal()); + } + sb.append(")"); + + return sb.toString(); + } + + + public void bind(DataBind b, Object value) throws SQLException { + if (value == null){ + b.setNull(Types.INTEGER); + } else { + Enum e = (Enum)value; + b.setInt(e.ordinal()); + } + } + + public Object read(DataReader dataReader) throws SQLException { + + Integer ordinal = dataReader.getInt(); + if (ordinal == null){ + return null; + } else { + if (ordinal < 0 || ordinal >= enumArray.length){ + String m = "Unexpected ordinal ["+ordinal+"] out of range ["+enumArray.length+"]"; + throw new IllegalStateException(m); + } + return enumArray[ordinal]; + } + } + + /** + * Convert the Boolean value to the db value. + */ + public Object toJdbcType(Object beanValue) { + if (beanValue == null) { + return null; + } + Enum e = (Enum)beanValue; + return e.ordinal(); + } + + /** + * Convert the db value to the Boolean value. + */ + public Object toBeanType(Object dbValue) { + if (dbValue == null) { + return null; + } + + int ordinal = ((Integer)dbValue).intValue(); + if (ordinal < 0 || ordinal >= enumArray.length){ + String m = "Unexpected ordinal ["+ordinal+"] out of range ["+enumArray.length+"]"; + throw new IllegalStateException(m); + } + return enumArray[ordinal]; + } + + } + + @SuppressWarnings({"rawtypes","unchecked"}) + public abstract static class EnumBase extends ScalarTypeBase { + + protected final Class enumType; + + public EnumBase(Class type, boolean jdbcNative, int jdbcType) { + super(type, jdbcNative, jdbcType); + this.enumType = type; + } + + public String format(Object t) { + return t.toString(); + } + + public String formatValue(Object t) { + return t.toString(); + } + + public Object parse(String value) { + return Enum.valueOf(enumType, value); + } + + public Object parseDateTime(long systemTimeMillis) { + throw new TextException("Not Supported"); + } + + public boolean isDateTimeCapable() { + return false; + } + + @Override + public Object jsonFromString(String value, JsonValueAdapter ctx) { + return parse(value); + } + + @Override + public String jsonToString(Object value, JsonValueAdapter ctx) { + return EscapeJson.escapeQuote(value.toString()); + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + String s = dataInput.readUTF(); + return parse(s); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + if (v == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeUTF(format(v)); + } + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java new file mode 100644 index 000000000..f31153d8a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.util.Iterator; + +/** + * Additional control over mapping to DB values. + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase implements ScalarType, ScalarTypeEnum { + + private final EnumToDbValueMap beanDbMap; + + private final int length; + + /** + * Create with an explicit mapping of bean to database values. + */ + public ScalarTypeEnumWithMapping(EnumToDbValueMap beanDbMap, Class enumType, int length) { + super(enumType, false, beanDbMap.getDbType()); + this.beanDbMap = beanDbMap; + this.length = length; + } + + /** + * Return the IN values for DB constraint construction. + */ + public String getContraintInValues() { + + StringBuilder sb = new StringBuilder(); + + int i = 0; + + sb.append("("); + + Iterator it = beanDbMap.dbValues(); + while (it.hasNext()) { + Object dbValue = it.next(); + if (i++ > 0) { + sb.append(","); + } + if (!beanDbMap.isIntegerType()) { + sb.append("'"); + } + sb.append(dbValue.toString()); + if (!beanDbMap.isIntegerType()) { + sb.append("'"); + } + } + + sb.append(")"); + + return sb.toString(); + } + + /** + * Return the DB column length for storing the enum value. + *

    + * This is for enum's mapped to strings. + *

    + */ + public int getLength() { + return length; + } + + public void bind(DataBind b, Object value) throws SQLException { + beanDbMap.bind(b, value); + } + + public Object read(DataReader dataReader) throws SQLException { + return beanDbMap.read(dataReader); + } + + public Object toBeanType(Object dbValue) { + return beanDbMap.getBeanValue(dbValue); + } + + public Object toJdbcType(Object beanValue) { + return beanDbMap.getDbValue(beanValue); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java new file mode 100644 index 000000000..4872a8378 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Float and float. + */ +public class ScalarTypeFloat extends ScalarTypeBase { + + public ScalarTypeFloat() { + super(Float.class, true, Types.REAL); + } + + public void bind(DataBind b, Float value) throws SQLException { + if (value == null){ + b.setNull(Types.REAL); + } else { + b.setFloat(value.floatValue()); + } + } + + public Float read(DataReader dataReader) throws SQLException { + + return dataReader.getFloat(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toFloat(value); + } + + public Float toBeanType(Object value) { + return BasicTypeConverter.toFloat(value); + } + + public String formatValue(Float t) { + return t.toString(); + } + + public Float parse(String value) { + return Float.valueOf(value); + } + + public Float parseDateTime(long systemTimeMillis) { + return Float.valueOf(systemTimeMillis); + } + + public boolean isDateTimeCapable() { + return true; + } + + public String toJsonString(Float value) { + if(value.isInfinite() || value.isNaN()) { + return "null"; + } else { + return value.toString(); + } + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + float val = dataInput.readFloat(); + return Float.valueOf(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Float value = (Float)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeFloat(value.floatValue()); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java new file mode 100644 index 000000000..2e521908f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Integer and int. + */ +public class ScalarTypeInteger extends ScalarTypeBase { + + public ScalarTypeInteger() { + super(Integer.class, true, Types.INTEGER); + } + + public void bind(DataBind b, Integer value) throws SQLException { + if (value == null){ + b.setNull(Types.INTEGER); + } else { + b.setInt(value.intValue()); + } + } + + public Integer read(DataReader dataReader) throws SQLException { + + return dataReader.getInt(); + } + + public Object readData(DataInput dataInput) throws IOException { + return Integer.valueOf(dataInput.readInt()); + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + dataOutput.writeInt((Integer) v); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toInteger(value); + } + + public Integer toBeanType(Object value) { + return BasicTypeConverter.toInteger(value); + } + + public String formatValue(Integer v) { + return v.toString(); + } + + public Integer parse(String value) { + return Integer.valueOf(value); + } + + public Integer parseDateTime(long systemTimeMillis) { + throw new TextException("Not Supported"); + } + + public boolean isDateTimeCapable() { + return false; + } + + public String jsonToString(Integer value, JsonValueAdapter ctx) { + return value.toString(); + } + + public Integer jsonFromString(String value, JsonValueAdapter ctx) { + return Integer.valueOf(value); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java new file mode 100644 index 000000000..6940a8f76 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Date; +import java.sql.Types; + +import org.joda.time.DateMidnight; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Joda DateMidnight. This maps to a JDBC Date. + */ +public class ScalarTypeJodaDateMidnight extends ScalarTypeBaseDate { + + /** + * Instantiates a new scalar type joda date midnight. + */ + public ScalarTypeJodaDateMidnight() { + super(DateMidnight.class, false, Types.DATE); + } + + @Override + public DateMidnight convertFromDate(Date ts) { + return new DateMidnight(ts.getTime()); + } + + @Override + public Date convertToDate(DateMidnight t) { + return new Date(t.getMillis()); + } + + public Object toJdbcType(Object value) { + if (value instanceof DateMidnight){ + return new Date(((DateMidnight)value).getMillis()); + } + return BasicTypeConverter.toDate(value); + } + + public DateMidnight toBeanType(Object value) { + if (value instanceof java.util.Date){ + return new DateMidnight(((java.util.Date)value).getTime()); + } + return (DateMidnight)value; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateTime.java new file mode 100644 index 000000000..8e622b7ba --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaDateTime.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Timestamp; +import java.sql.Types; + +import org.joda.time.DateTime; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Joda DateTime. This maps to a JDBC Timestamp. + */ +public class ScalarTypeJodaDateTime extends ScalarTypeBaseDateTime { + + public ScalarTypeJodaDateTime() { + super(DateTime.class, false, Types.TIMESTAMP); + } + + @Override + public DateTime convertFromTimestamp(Timestamp ts) { + return new DateTime(ts.getTime()); + } + + @Override + public Timestamp convertToTimestamp(DateTime t) { + return new Timestamp(t.getMillis()); + } + + public Object toJdbcType(Object value) { + if (value instanceof DateTime){ + return new Timestamp(((DateTime)value).getMillis()); + } + return BasicTypeConverter.toTimestamp(value); + } + + public DateTime toBeanType(Object value) { + if (value instanceof java.util.Date){ + return new DateTime(((java.util.Date)value).getTime()); + } + return (DateTime)value; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java new file mode 100644 index 000000000..fd72f3fa1 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Date; +import java.sql.Types; + +import org.joda.time.LocalDate; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Joda LocalDate. This maps to a JDBC Date. + */ +public class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate { + + public ScalarTypeJodaLocalDate() { + super(LocalDate.class, false, Types.DATE); + } + + @Override + public LocalDate convertFromDate(Date ts) { + return new LocalDate(((java.util.Date)ts).getTime()); + } + + @Override + public Date convertToDate(LocalDate t) { + return new java.sql.Date(t.toDateMidnight().getMillis()); + } + + public Object toJdbcType(Object value) { + if (value instanceof LocalDate){ + return new java.sql.Date(((LocalDate)value).toDateMidnight().getMillis()); + } + return BasicTypeConverter.toDate(value); + } + + public LocalDate toBeanType(Object value) { + if (value instanceof java.util.Date){ + return new LocalDate(((java.util.Date)value).getTime()); + } + return (LocalDate)value; + } + + public LocalDate parseDateTime(long systemTimeMillis) { + return new LocalDate(systemTimeMillis); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDateTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDateTime.java new file mode 100644 index 000000000..d59dd59d6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalDateTime.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Timestamp; +import java.sql.Types; + +import org.joda.time.LocalDateTime; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Joda LocalDateTime. This maps to a JDBC Timestamp. + */ +public class ScalarTypeJodaLocalDateTime extends ScalarTypeBaseDateTime { + + public ScalarTypeJodaLocalDateTime() { + super(LocalDateTime.class, false, Types.TIMESTAMP); + } + + @Override + public LocalDateTime convertFromTimestamp(Timestamp ts) { + return new LocalDateTime(ts.getTime()); + } + + @Override + public Timestamp convertToTimestamp(LocalDateTime t) { + return new Timestamp(t.toDateTime().getMillis()); + } + + public Object toJdbcType(Object value) { + if (value instanceof LocalDateTime){ + return new Timestamp(((LocalDateTime)value).toDateTime().getMillis()); + } + return BasicTypeConverter.toTimestamp(value); + } + + public LocalDateTime toBeanType(Object value) { + if (value instanceof java.util.Date){ + return new LocalDateTime(((java.util.Date)value).getTime()); + } + return (LocalDateTime)value; + } + + public LocalDateTime parseDateTime(long systemTimeMillis) { + return new LocalDateTime(systemTimeMillis); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java new file mode 100644 index 000000000..c51dc46e7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Types; + +import org.joda.time.DateTimeZone; +import org.joda.time.LocalTime; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Joda LocalTime. This maps to a JDBC Time. + */ +public class ScalarTypeJodaLocalTime extends ScalarTypeBase { + + public ScalarTypeJodaLocalTime() { + super(LocalTime.class, false, Types.TIME); + } + + public void bind(DataBind b, LocalTime value) throws SQLException { + if (value == null){ + b.setNull(Types.TIME); + } else { + Time sqlTime = new Time(value.getMillisOfDay()); + b.setTime(sqlTime); + } + } + + public LocalTime read(DataReader dataReader) throws SQLException { + + Time sqlTime = dataReader.getTime(); + if (sqlTime == null){ + return null; + } else { + return new LocalTime(sqlTime, DateTimeZone.UTC); + } + } + + public Object toJdbcType(Object value) { + if (value instanceof LocalTime){ + return new Time(((LocalTime)value).getMillisOfDay()); + } + return BasicTypeConverter.toTime(value); + } + + public LocalTime toBeanType(Object value) { + if (value instanceof java.util.Date){ + return new LocalTime(value, DateTimeZone.UTC); + } + return (LocalTime)value; + } + + public String formatValue(LocalTime v) { + return v.toString(); + } + + public LocalTime parse(String value) { + return new LocalTime(value); + } + + public LocalTime parseDateTime(long systemTimeMillis) { + return new LocalTime(systemTimeMillis); + } + + public boolean isDateTimeCapable() { + return true; + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + String val = dataInput.readUTF(); + return parse(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Time value = (Time)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeUTF(format(value)); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapBoolean.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapBoolean.java new file mode 100644 index 000000000..a8edb0a9b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapBoolean.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2009 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + + +/** + * ScalarType for LDAP Boolean. + */ +public class ScalarTypeLdapBoolean extends ScalarTypeBoolean.StringBoolean { + + public ScalarTypeLdapBoolean() { + super("TRUE", "FALSE"); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapDate.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapDate.java new file mode 100644 index 000000000..1ecb33026 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapDate.java @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * Wrapper type that wraps all java.sql.Date types for LDAP. + * + * @author rbygrave + */ +public class ScalarTypeLdapDate implements ScalarType { + + private static final String timestampLDAPFormat = "yyyyMMddHHmmss'Z'"; + + private final ScalarType baseType; + + public ScalarTypeLdapDate(ScalarType baseType) { + this.baseType = baseType; + } + + public T toBeanType(Object value) { + if (value == null){ + return null; + } + if (value instanceof String == false){ + String msg = "Expecting a String type but got "+value.getClass()+" value["+value+"]"; + throw new PersistenceException(msg); + } + try { + SimpleDateFormat sdf = new SimpleDateFormat(timestampLDAPFormat); + Date date = sdf.parse((String)value); + + return baseType.parseDateTime(date.getTime()); + + } catch (Exception e) { + String msg = "Error parsing LDAP timestamp "+value; + throw new PersistenceException(msg, e); + } + } + + public Object toJdbcType(Object value) { + + if (value == null){ + return null; + } + + Object ts = baseType.toJdbcType(value); + if (ts instanceof java.sql.Date == false){ + String msg = "Expecting a java.sql.Date type but got "+value.getClass()+" value["+value+"]"; + throw new PersistenceException(msg); + } + + java.sql.Date t = (java.sql.Date)ts; + SimpleDateFormat sdf = new SimpleDateFormat(timestampLDAPFormat); + return sdf.format(t); + } + + + public void bind(DataBind b, T value) throws SQLException { + baseType.bind(b, value); + } + + public int getJdbcType() { + return Types.VARCHAR; + } + + public int getLength() { + return baseType.getLength(); + } + + public Class getType() { + return baseType.getType(); + } + + public boolean isDateTimeCapable() { + return baseType.isDateTimeCapable(); + } + + public boolean isJdbcNative() { + return false; + } + + public void loadIgnore(DataReader dataReader) { + baseType.loadIgnore(dataReader); + } + + public String format(Object v) { + return baseType.format(v); + } + + public String formatValue(T t) { + return baseType.formatValue(t); + } + + public T parse(String value) { + return baseType.parse(value); + } + + public T parseDateTime(long systemTimeMillis) { + return baseType.parseDateTime(systemTimeMillis); + } + + public T read(DataReader dataReader) throws SQLException { + return baseType.read(dataReader); + } + + public void accumulateScalarTypes(String propName, CtCompoundTypeScalarList list) { + baseType.accumulateScalarTypes(propName, list); + } + + public String jsonToString(T value, JsonValueAdapter ctx) { + return baseType.jsonToString(value, ctx); + } + + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx) { + baseType.jsonWrite(buffer, value, ctx); + } + + public T jsonFromString(String value, JsonValueAdapter ctx) { + return baseType.jsonFromString(value, ctx); + } + + public Object readData(DataInput dataInput) throws IOException { + return baseType.readData(dataInput); + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + baseType.writeData(dataOutput, v); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapTimestamp.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapTimestamp.java new file mode 100644 index 000000000..48c2c1c73 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLdapTimestamp.java @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * Wrapper type that wraps all java.sql.Timestamp types for LDAP. + * + * @author rbygrave + */ +public class ScalarTypeLdapTimestamp implements ScalarType { + + private static final String timestampLDAPFormat = "yyyyMMddHHmmss'Z'"; + + private final ScalarType baseType; + + public ScalarTypeLdapTimestamp(ScalarType baseType) { + this.baseType = baseType; + } + + public T toBeanType(Object value) { + if (value == null){ + return null; + } + if (value instanceof String == false){ + String msg = "Expecting a String type but got "+value.getClass()+" value["+value+"]"; + throw new PersistenceException(msg); + } + try { + SimpleDateFormat sdf = new SimpleDateFormat(timestampLDAPFormat); + Date date = sdf.parse((String)value); + + return baseType.parseDateTime(date.getTime()); + + } catch (Exception e) { + String msg = "Error parsing LDAP timestamp "+value; + throw new PersistenceException(msg, e); + } + } + + public Object toJdbcType(Object value) { + + if (value == null){ + return null; + } + + Object ts = baseType.toJdbcType(value); + if (ts instanceof java.sql.Timestamp == false){ + String msg = "Expecting a Timestamp type but got "+value.getClass()+" value["+value+"]"; + throw new PersistenceException(msg); + } + + Timestamp t = (Timestamp)ts; + SimpleDateFormat sdf = new SimpleDateFormat(timestampLDAPFormat); + return sdf.format(t); + } + + + public void bind(DataBind b, T value) throws SQLException { + baseType.bind(b, value); + } + + public int getJdbcType() { + return Types.VARCHAR; + } + + public int getLength() { + return baseType.getLength(); + } + + public Class getType() { + return baseType.getType(); + } + + public boolean isDateTimeCapable() { + return baseType.isDateTimeCapable(); + } + + public boolean isJdbcNative() { + return false; + } + + public void loadIgnore(DataReader dataReader) { + baseType.loadIgnore(dataReader); + } + + public String format(Object v) { + return baseType.format(v); + } + + public String formatValue(T t) { + return baseType.formatValue(t); + } + + public T parse(String value) { + return baseType.parse(value); + } + + public T parseDateTime(long systemTimeMillis) { + return baseType.parseDateTime(systemTimeMillis); + } + + public T read(DataReader dataReader) throws SQLException { + return baseType.read(dataReader); + } + + public void accumulateScalarTypes(String propName, CtCompoundTypeScalarList list) { + baseType.accumulateScalarTypes(propName, list); + } + + public String jsonToString(T value, JsonValueAdapter ctx) { + return baseType.jsonToString(value, ctx); + } + + public void jsonWrite(WriteJsonBuffer buffer, T value, JsonValueAdapter ctx) { + baseType.jsonWrite(buffer, value, ctx); + } + + public T jsonFromString(String value, JsonValueAdapter ctx) { + return baseType.jsonFromString(value, ctx); + } + + public Object readData(DataInput dataInput) throws IOException { + return baseType.readData(dataInput); + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + baseType.writeData(dataOutput, v); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocale.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocale.java new file mode 100644 index 000000000..9afb54521 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocale.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.util.Locale; + +/** + * ScalarType for java.util.Currency which converts to and from a VARCHAR + * database column. + */ +public class ScalarTypeLocale extends ScalarTypeBaseVarchar { + + public ScalarTypeLocale() { + super(Locale.class); + } + + @Override + public int getLength() { + return 20; + } + + @Override + public Locale convertFromDbString(String dbValue) { + return parse(dbValue); + } + + @Override + public String convertToDbString(Locale beanValue) { + return ((Locale) beanValue).toString(); + } + + public String formatValue(Locale t) { + return t.toString(); + } + + public Locale parse(String value) { + + int pos1 = -1; + int pos2 = -1; + + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '_') { + if (pos1 > -1) { + pos2 = i; + break; + } else { + pos1 = i; + } + } + } + if (pos1 == -1) { + return new Locale(value); + } + String language = value.substring(0, pos1); + if (pos2 == -1) { + String country = value.substring(pos1 + 1); + return new Locale(language, country); + } else { + String country = value.substring(pos1 + 1, pos2); + String variant = value.substring(pos2 + 1); + return new Locale(language, country, variant); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java new file mode 100644 index 000000000..c66f306fc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Long and long. + */ +public class ScalarTypeLong extends ScalarTypeBase { + + public ScalarTypeLong() { + super(Long.class, true, Types.BIGINT); + } + + public void bind(DataBind b, Long value) throws SQLException { + if (value == null){ + b.setNull(Types.BIGINT); + } else { + b.setLong(value.longValue()); + } + } + + public Long read(DataReader dataReader) throws SQLException { + + return dataReader.getLong(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toLong(value); + } + + public Long toBeanType(Object value) { + return BasicTypeConverter.toLong(value); + } + + public String formatValue(Long t) { + return t.toString(); + } + + public Long parse(String value) { + return Long.valueOf(value); + } + + public Long parseDateTime(long systemTimeMillis) { + return Long.valueOf(systemTimeMillis); + } + + public boolean isDateTimeCapable() { + return true; + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + long val = dataInput.readLong(); + return Long.valueOf(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Long value = (Long)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeLong(value.longValue()); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongToTimestamp.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongToTimestamp.java new file mode 100644 index 000000000..813dc5502 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongToTimestamp.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.Timestamp; + +public class ScalarTypeLongToTimestamp extends ScalarTypeWrapper { + + public ScalarTypeLongToTimestamp() { + super(Long.class, new ScalarTypeTimestamp(), new LongToTimestampConverter()); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongVarchar.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongVarchar.java new file mode 100644 index 000000000..0012ebecf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLongVarchar.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for String. + */ +public class ScalarTypeLongVarchar extends ScalarTypeClob { + + public ScalarTypeLongVarchar() { + super(true, Types.LONGVARCHAR); + } + + @Override + public String read(DataReader dataReader) throws SQLException { + + return dataReader.getStringFromStream(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java new file mode 100644 index 000000000..6a689adb0 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.math.BigInteger; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for java.math.BigInteger. + */ +public class ScalarTypeMathBigInteger extends ScalarTypeBase { + + public ScalarTypeMathBigInteger() { + super(BigInteger.class, false, Types.BIGINT); + } + + public void bind(DataBind b, BigInteger value) throws SQLException { + if (value == null){ + b.setNull(Types.BIGINT); + } else { + b.setLong(value.longValue()); + } + } + + public BigInteger read(DataReader dataReader) throws SQLException { + + Long l = dataReader.getLong(); + if (l == null){ + return null; + } + return new BigInteger(String.valueOf(l)); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toLong(value); + } + + public BigInteger toBeanType(Object value) { + return BasicTypeConverter.toMathBigInteger(value); + } + + + public String formatValue(BigInteger v) { + return v.toString(); + } + + public BigInteger parse(String value) { + return new BigInteger(value); + } + + public BigInteger parseDateTime(long systemTimeMillis) { + return BigInteger.valueOf(systemTimeMillis); + } + + public boolean isDateTimeCapable() { + return true; + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + long val = dataInput.readLong(); + return Long.valueOf(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Long value = (Long)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeLong(value.longValue()); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeScalaDouble.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeScalaDouble.java new file mode 100644 index 000000000..0b509581d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeScalaDouble.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2009 Authors + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import scala.Double; + +import com.avaje.ebean.config.ScalarTypeConverter; + +public class ScalarTypeScalaDouble extends ScalarTypeWrapper { + + public ScalarTypeScalaDouble() { + super(Object.class, new ScalarTypeDouble(), new Converter()); + } + + static class Converter implements ScalarTypeConverter { + + public Double getNullValue() { + return null; + } + + public Object wrapValue(java.lang.Double scalarType) { + return scalarType; + } + + public java.lang.Double unwrapValue(Object beanType) { + return ((scala.Double)beanType).toDouble(); + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java new file mode 100644 index 000000000..adbe881bf --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.TextException; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for Short and short. + */ +public class ScalarTypeShort extends ScalarTypeBase { + + public ScalarTypeShort() { + super(Short.class, true, Types.SMALLINT); + } + + public void bind(DataBind b, Short value) throws SQLException { + if (value == null){ + b.setNull(Types.SMALLINT); + } else { + b.setShort(value.shortValue()); + } + } + + public Short read(DataReader dataReader) throws SQLException { + + return dataReader.getShort(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toShort(value); + } + + public Short toBeanType(Object value) { + return BasicTypeConverter.toShort(value); + } + + public String formatValue(Short v) { + return v.toString(); + } + + public Short parse(String value) { + return Short.valueOf(value); + } + + public Short parseDateTime(long systemTimeMillis) { + throw new TextException("Not Supported"); + } + + public boolean isDateTimeCapable() { + return false; + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + short val = dataInput.readShort(); + return Short.valueOf(val); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + Short value = (Short)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeShort(value.shortValue()); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java new file mode 100644 index 000000000..048f51d34 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; + +import com.avaje.ebean.text.json.JsonValueAdapter; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; +import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; + +/** + * ScalarType for String. + */ +public class ScalarTypeString extends ScalarTypeBase { + + public ScalarTypeString() { + super(String.class, true, Types.VARCHAR); + } + + public void bind(DataBind b, String value) throws SQLException { + if (value == null){ + b.setNull(Types.VARCHAR); + } else { + b.setString(value); + } + } + + public String read(DataReader dataReader) throws SQLException { + + return dataReader.getString(); + } + + public Object toJdbcType(Object value) { + return BasicTypeConverter.toString(value); + } + + public String toBeanType(Object value) { + return BasicTypeConverter.toString(value); + } + + public String formatValue(String t) { + return t; + } + + public String parse(String value) { + return value; + } + + public String parseDateTime(long systemTimeMillis) { + return String.valueOf(systemTimeMillis); + } + + public boolean isDateTimeCapable() { + return true; + } + + + @Override + public void jsonWrite(WriteJsonBuffer buffer, String value, JsonValueAdapter ctx) { + String s = format(value); + EscapeJson.escapeQuote(s, buffer); + } + + @Override + public String jsonFromString(String value, JsonValueAdapter ctx) { + return value; + } + + @Override + public String jsonToString(String value, JsonValueAdapter ctx) { + return EscapeJson.escapeQuote(value); + } + + public Object readData(DataInput dataInput) throws IOException { + if (!dataInput.readBoolean()) { + return null; + } else { + return dataInput.readUTF(); + } + } + + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + String value = (String)v; + if (value == null){ + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + dataOutput.writeUTF(value); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java new file mode 100644 index 000000000..0049e3988 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2006 Robin Bygrave + * + * This file is part of Ebean. + * + * Ebean is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Ebean is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Ebean; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Types; + +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * ScalarType for java.sql.Time. + */ +public class ScalarTypeTime extends ScalarTypeBase