diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87d82fdf71..a157cac46f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ on the Jira HHH-123 : `git checkout -b HHH-123 master` ## Code -Do yo thang! +Do yo thing! ## Commit diff --git a/build.gradle b/build.gradle index bfc15ca270..0db532b861 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,8 @@ subprojects { subProject -> apply plugin: 'java' apply plugin: 'maven' // for install task as well as deploy dependencies apply plugin: 'uploadAuth' + apply plugin: 'osgi' + apply from: "../utilities.gradle" configurations { provided { @@ -165,13 +167,54 @@ subprojects { subProject -> compileJava.options.define(compilerArgs: ["-proc:none", "-encoding", "UTF-8"]) compileTestJava.options.define(compilerArgs: ["-proc:none", "-encoding", "UTF-8"]) - manifest.mainAttributes( - provider: 'gradle', - 'Implementation-Url': 'http://hibernate.org', - 'Implementation-Version': version, - 'Implementation-Vendor': 'Hibernate.org', - 'Implementation-Vendor-Id': 'org.hibernate' - ) + jar { + Set exportPackages = new HashSet() + Set privatePackages = new HashSet() + + // TODO: Could more of this be pulled into utilities.gradle? + sourceSets.each { SourceSet sourceSet -> + // skip certain source sets + if ( ! ['test','matrix'].contains( sourceSet.name ) ) { + sourceSet.java.each { javaFile -> + // - org.hibernate.boot.registry.classloading.internal + // until EntityManagerFactoryBuilderImpl no longer imports ClassLoaderServiceImpl + // - .util for external module use (especially envers) + final String[] temporaryExports = [ + 'org.hibernate.boot.registry.classloading.internal', + 'org.hibernate.internal.util' ] + + final String packageName = determinePackageName( sourceSet.java, javaFile ); + if ( ! temporaryExports.contains( packageName ) + && ( packageName.endsWith( ".internal" ) + || packageName.contains( ".internal." ) + || packageName.endsWith( ".test" ) + || packageName.contains( ".test." ) ) ) { + privatePackages.add( packageName ); + } + else { + exportPackages.add( packageName ); + } + } + } + } + + manifest = osgiManifest { + // GRADLE-1411: Even if we override Imports and Exports + // auto-generation with instructions, classesDir and classpath + // need to be here (temporarily). + classesDir = sourceSets.main.output.classesDir + classpath = configurations.runtime + + instruction 'Export-Package', exportPackages.toArray(new String[0]) + instruction 'Private-Package', privatePackages.toArray(new String[0]) + instruction 'Bundle-Vendor', 'Hibernate.org' + + instruction 'Implementation-Url', 'http://hibernate.org' + instruction 'Implementation-Version', version + instruction 'Implementation-Vendor', 'Hibernate.org' + instruction 'Implementation-Vendor-Id', 'org.hibernate' + } + } tasks.withType(Test) { System.getProperties().each { Map.Entry entry -> diff --git a/documentation/src/main/docbook/devguide/en-US/Caching.xml b/documentation/src/main/docbook/devguide/en-US/Caching.xml index 213b39cecf..d2894b3812 100644 --- a/documentation/src/main/docbook/devguide/en-US/Caching.xml +++ b/documentation/src/main/docbook/devguide/en-US/Caching.xml @@ -383,42 +383,12 @@ read-only nontrict read-write read-write - - - - - OSCache - - - - read-only - nontrict read-write - read-write - - - - - SwarmCache - - - - read-only - nontrict read-write - - - - - JBoss Cache 1.x - - - - read-only transactional - JBoss Cache 2.x + Infinispan diff --git a/documentation/src/main/docbook/devguide/en-US/Data_Categorizations.xml b/documentation/src/main/docbook/devguide/en-US/Data_Categorizations.xml index 0b7d39806d..643b8f758e 100644 --- a/documentation/src/main/docbook/devguide/en-US/Data_Categorizations.xml +++ b/documentation/src/main/docbook/devguide/en-US/Data_Categorizations.xml @@ -35,6 +35,7 @@ + @@ -294,7 +295,111 @@ - +
+ National Character Types + + National Character types, which is a new feature since JDBC 4.0 API, now available in hibernate type system. + National Language Support enables you retrieve data or insert data into a database in any character + set that the underlying database supports. + + + + Depending on your environment, you might want to set the configuration option hibernate.use_nationalized_character_data + to true and having all string or clob based attributes having this national character support automatically. + There is nothing else to be changed, and you don't have to use any hibernate specific mapping, so it is portable + ( though the national character support feature is not required and may not work on other JPA provider impl ). + + + + The other way of using this feature is having the @Nationalized annotation on the attribute + that should be nationalized. This only works on string based attributes, including string, char, char array and clob. + + + @Entity( name="NationalizedEntity") + public static class NationalizedEntity { + @Id + private Integer id; + + @Nationalized + private String nvarcharAtt; + + @Lob + @Nationalized + private String materializedNclobAtt; + + @Lob + @Nationalized + private NClob nclobAtt; + + @Nationalized + private Character ncharacterAtt; + + @Nationalized + private Character[] ncharArrAtt; + + @Type(type = "ntext") + private String nlongvarcharcharAtt; + } + + + + National Character Type Mappings + + + + Hibernate type + Database type + JDBC type + Type registry + + + + + org.hibernate.type.StringNVarcharType + string + NVARCHAR + nstring + + + org.hibernate.type.NTextType + string + LONGNVARCHAR + materialized_clob + + + org.hibernate.type.NClobType + java.sql.NClob + NCLOB + nclob + + + org.hibernate.type.MaterializedNClobType + string + NCLOB + materialized_nclob + + + org.hibernate.type.PrimitiveCharacterArrayNClobType + char[] + NCHAR + char[] + + + org.hibernate.type.CharacterNCharType + java.lang.Character + NCHAR + ncharacter + + + org.hibernate.type.CharacterArrayNClobType + java.lang.Character[] + NCLOB + Character[], java.lang.Character[] + + + +
+
Composite types diff --git a/documentation/src/main/docbook/devguide/en-US/Envers.xml b/documentation/src/main/docbook/devguide/en-US/Envers.xml index 7f9f05cb37..d2e1fd7d0a 100644 --- a/documentation/src/main/docbook/devguide/en-US/Envers.xml +++ b/documentation/src/main/docbook/devguide/en-US/Envers.xml @@ -1306,7 +1306,8 @@ query.add(AuditEntity.relatedId("address").eq(relatedEntityId));]]> - collections of components + Bag style collection which identifier column has been defined using + @CollectionId annotation (JIRA ticket HHH-3950). diff --git a/documentation/src/main/docbook/devguide/en-US/Locking.xml b/documentation/src/main/docbook/devguide/en-US/Locking.xml index 492adba889..245feb7a3e 100644 --- a/documentation/src/main/docbook/devguide/en-US/Locking.xml +++ b/documentation/src/main/docbook/devguide/en-US/Locking.xml @@ -263,6 +263,11 @@ acquired upon explicit user request using a SELECT ... FOR UPDATE NOWAIT in Oracle. + + LockMode.UPGRADE_SKIPLOCKED + acquired upon explicit user request using a SELECT ... FOR UPDATE SKIP LOCKED in + Oracle, or SELECT ... with (rowlock,updlock,readpast) in SQL Server. + LockMode.READ acquired automatically when Hibernate reads data under Repeatable Read or @@ -299,17 +304,18 @@ - If you call Session.load() with option or - , and the requested object is not already loaded by the session, the object is - loaded using SELECT ... FOR UPDATE. If you call load() for an object that - is already loaded with a less restrictive lock than the one you request, Hibernate calls - lock() for that object. + If you call Session.load() with option , + or , and the requested object is not already + loaded by the session, the object is loaded using SELECT ... FOR UPDATE. If you call + load() for an object that is already loaded with a less restrictive lock than the one + you request, Hibernate calls lock() for that object. Session.lock() performs a version number check if the specified lock mode is - READ, UPGRADE, or UPGRADE_NOWAIT. In the case of - UPGRADE or UPGRADE_NOWAIT, SELECT ... FOR UPDATE syntax is - used. + READ, UPGRADE, UPGRADE_NOWAIT or + UPGRADE_SKIPLOCKED. In the case of UPGRADE, + UPGRADE_NOWAIT or UPGRADE_SKIPLOCKED, SELECT ... FOR UPDATE + syntax is used. If the requested lock mode is not supported by the database, Hibernate uses an appropriate alternate mode diff --git a/documentation/src/main/docbook/devguide/en-US/appendices/legacy_criteria/Legacy_Criteria.xml b/documentation/src/main/docbook/devguide/en-US/appendices/legacy_criteria/Legacy_Criteria.xml index 1cbe9496ff..7a27fb6851 100644 --- a/documentation/src/main/docbook/devguide/en-US/appendices/legacy_criteria/Legacy_Criteria.xml +++ b/documentation/src/main/docbook/devguide/en-US/appendices/legacy_criteria/Legacy_Criteria.xml @@ -119,7 +119,7 @@ List cats = sess.createCriteria(Cat.class) diff --git a/documentation/src/main/docbook/devguide/en-US/appendix-Configuration_Properties.xml b/documentation/src/main/docbook/devguide/en-US/appendix-Configuration_Properties.xml index 7a0d75a254..16a98e5e99 100644 --- a/documentation/src/main/docbook/devguide/en-US/appendix-Configuration_Properties.xml +++ b/documentation/src/main/docbook/devguide/en-US/appendix-Configuration_Properties.xml @@ -125,6 +125,12 @@ Forces Hibernate to order SQL updates by the primary key value of the items being updated. This reduces the likelihood of transaction deadlocks in highly-concurrent systems. + + hibernate.order_by.default_null_ordering + none, first or last + Defines precedence of null values in ORDER BY clause. Defaults to + none which varies between RDBMS implementation. + hibernate.generate_statistics true or false diff --git a/documentation/src/main/docbook/devguide/en-US/chapters/query_ql/HQL_JPQL.xml b/documentation/src/main/docbook/devguide/en-US/chapters/query_ql/HQL_JPQL.xml index 5aaf71c6ae..ad4006c732 100644 --- a/documentation/src/main/docbook/devguide/en-US/chapters/query_ql/HQL_JPQL.xml +++ b/documentation/src/main/docbook/devguide/en-US/chapters/query_ql/HQL_JPQL.xml @@ -1427,7 +1427,9 @@ Individual expressions in the order-by can be qualified with either ASC (ascending) or - DESC (descending) to indicated the desired ordering direction. + DESC (descending) to indicated the desired ordering direction. Null values can be placed + in front or at the end of sorted set using NULLS FIRST or NULLS LAST + clause respectively. Order-by examples diff --git a/documentation/src/main/docbook/devguide/en-US/chapters/services/Services.xml b/documentation/src/main/docbook/devguide/en-US/chapters/services/Services.xml index 66441367bc..49fb85f27b 100644 --- a/documentation/src/main/docbook/devguide/en-US/chapters/services/Services.xml +++ b/documentation/src/main/docbook/devguide/en-US/chapters/services/Services.xml @@ -261,7 +261,7 @@ The standard resolver implementation acts as a chain, delegating to a series of individual resolvers. The standard Hibernate resolution behavior is contained in - org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver. + org.hibernate.engine.jdbc.dialect.internal.StandardDatabaseMetaDataDialectResolver. org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator also consults with the hibernate.dialect_resolvers setting for any custom resolvers. diff --git a/documentation/src/main/docbook/manual/en-US/content/batch.xml b/documentation/src/main/docbook/manual/en-US/content/batch.xml index 4ef5a23791..0754b972ca 100755 --- a/documentation/src/main/docbook/manual/en-US/content/batch.xml +++ b/documentation/src/main/docbook/manual/en-US/content/batch.xml @@ -226,7 +226,7 @@ session.close();]]> In keeping with the EJB3 specification, HQL UPDATE statements, by default, do not effect the - version + version or the timestamp property values for the affected entities. However, you can force Hibernate to reset the version or diff --git a/documentation/src/main/docbook/manual/en-US/content/collection_mapping.xml b/documentation/src/main/docbook/manual/en-US/content/collection_mapping.xml index faebbd1cb3..3fe09b797d 100644 --- a/documentation/src/main/docbook/manual/en-US/content/collection_mapping.xml +++ b/documentation/src/main/docbook/manual/en-US/content/collection_mapping.xml @@ -483,8 +483,8 @@ public class Part { @javax.persistence.OrderBy to your property. This annotation takes as parameter a list of comma separated properties (of the target entity) and orders the collection accordingly (eg - firstname asc, age desc), if the string is empty, the - collection will be ordered by the primary key of the target + firstname asc, age desc, weight asc nulls last), if the string + is empty, the collection will be ordered by the primary key of the target entity. diff --git a/documentation/src/main/docbook/manual/en-US/content/performance.xml b/documentation/src/main/docbook/manual/en-US/content/performance.xml index 802fcd5945..5d48b5a81a 100644 --- a/documentation/src/main/docbook/manual/en-US/content/performance.xml +++ b/documentation/src/main/docbook/manual/en-US/content/performance.xml @@ -747,53 +747,6 @@ Customer customer = (Customer) session.get( Customer.class, customerId ); yes - Infinispan @@ -1122,53 +1075,6 @@ public SortedSet<Ticket> getTickets() { yes - Infinispan diff --git a/documentation/src/main/docbook/manual/en-US/content/query_hql.xml b/documentation/src/main/docbook/manual/en-US/content/query_hql.xml index c020cc4729..f1f4bce546 100644 --- a/documentation/src/main/docbook/manual/en-US/content/query_hql.xml +++ b/documentation/src/main/docbook/manual/en-US/content/query_hql.xml @@ -855,12 +855,17 @@ WHERE prod.name = 'widget' +order by cat.name asc, cat.weight desc nulls first, cat.birthdate]]> The optional asc or desc indicate ascending or descending order respectively. + + + The optional nulls first or nulls last indicate precedence of null + values while sorting. +
diff --git a/documentation/src/main/docbook/manual/en-US/content/tutorial.xml b/documentation/src/main/docbook/manual/en-US/content/tutorial.xml index 6090dc265e..8b04e070ca 100644 --- a/documentation/src/main/docbook/manual/en-US/content/tutorial.xml +++ b/documentation/src/main/docbook/manual/en-US/content/tutorial.xml @@ -51,7 +51,7 @@ The first thing we need to do is to set up the development environment. We will be using the "standard layout" advocated by alot of build tools such - as Maven. + as Maven. Maven, in particular, has a good resource describing this layout. diff --git a/hibernate-c3p0/hibernate-c3p0.gradle b/hibernate-c3p0/hibernate-c3p0.gradle index c68d582ff0..3d19fe5db2 100644 --- a/hibernate-c3p0/hibernate-c3p0.gradle +++ b/hibernate-c3p0/hibernate-c3p0.gradle @@ -8,4 +8,11 @@ dependencies { transitive = true } testCompile project( ':hibernate-testing' ) +} + +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM C3P0' + instruction 'Bundle-SymbolicName', 'org.hibernate.c3p0' + } } \ No newline at end of file diff --git a/hibernate-core/hibernate-core.gradle b/hibernate-core/hibernate-core.gradle index 8a7b485a26..3f65833c23 100644 --- a/hibernate-core/hibernate-core.gradle +++ b/hibernate-core/hibernate-core.gradle @@ -37,6 +37,28 @@ manifest.mainAttributes( 'Main-Class': 'org.hibernate.Version' ) +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM Core' + instruction 'Bundle-SymbolicName', 'org.hibernate.core' + + instruction 'Import-Package', + 'javax.security.auth;resolution:=optional', + 'javax.security.jacc;resolution:=optional', + 'javax.validation;resolution:=optional', + 'javax.validation.constraints;resolution:=optional', + 'javax.validation.groups;resolution:=optional', + 'javax.validation.metadata;resolution:=optional', + '*' + + // TODO: Uncomment once EntityManagerFactoryBuilderImpl no longer + // uses ClassLoaderServiceImpl. + instruction 'Export-Package', + 'org.hibernate.boot.registry.classloading.internal', + '*' + } +} + sourceSets.main { ext.jaxbTargetDir = file( "${buildDir}/generated-src/jaxb/main" ) java.srcDir jaxbTargetDir diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index 80af06eb79..fbeb556da7 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -348,9 +348,18 @@ orderClause ; orderExprs - : orderExpr ( ASCENDING | DESCENDING )? (orderExprs)? + : orderExpr ( ASCENDING | DESCENDING )? ( nullOrdering )? (orderExprs)? ; +nullOrdering + : NULLS nullPrecedence + ; + +nullPrecedence + : FIRST + | LAST + ; + orderExpr : { isOrderExpressionResultVariableRef( _t ) }? resultVariableRef | expr diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index efa86e7cbc..ff725b3f0c 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -78,6 +78,9 @@ tokens UPDATE="update"; VERSIONED="versioned"; WHERE="where"; + NULLS="nulls"; + FIRST; + LAST; // -- SQL tokens -- // These aren't part of HQL, but the SQL fragment parser uses the HQL lexer, so they need to be declared here. @@ -439,7 +442,7 @@ orderByClause ; orderElement - : expression ( ascendingOrDescending )? + : expression ( ascendingOrDescending )? ( nullOrdering )? ; ascendingOrDescending @@ -447,6 +450,24 @@ ascendingOrDescending | ( "desc" | "descending") { #ascendingOrDescending.setType(DESCENDING); } ; +nullOrdering + : NULLS nullPrecedence + ; + +nullPrecedence + : IDENT { + if ( "first".equalsIgnoreCase( #nullPrecedence.getText() ) ) { + #nullPrecedence.setType( FIRST ); + } + else if ( "last".equalsIgnoreCase( #nullPrecedence.getText() ) ) { + #nullPrecedence.setType( LAST ); + } + else { + throw new SemanticException( "Expecting 'first' or 'last', but found '" + #nullPrecedence.getText() + "' as null ordering precedence." ); + } + } + ; + //## havingClause: //## HAVING logicalExpression; @@ -723,7 +744,7 @@ castedIdentPrimaryBase aggregate : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! additiveExpression CLOSE! { #aggregate.setType(AGGREGATE); } // Special case for count - It's 'parameters' can be keywords. - | COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr | NUM_INT ) ) ) CLOSE! + | COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr | NUM_INT | caseExpression ) ) ) CLOSE! | collectionExpr ; @@ -737,6 +758,7 @@ collectionExpr compoundExpr : collectionExpr | path + | { LA(1) == OPEN && LA(2) == CLOSE }? OPEN! CLOSE! | (OPEN! ( (expression (COMMA! expression)*) | subQuery ) CLOSE!) | parameter ; diff --git a/hibernate-core/src/main/antlr/order-by-render.g b/hibernate-core/src/main/antlr/order-by-render.g index f4e7fe4e3f..fd8f9f82af 100644 --- a/hibernate-core/src/main/antlr/order-by-render.g +++ b/hibernate-core/src/main/antlr/order-by-render.g @@ -30,6 +30,7 @@ package org.hibernate.sql.ordering.antlr; * Antlr grammar for rendering ORDER_BY trees as described by the {@link OrderByFragmentParser} * @author Steve Ebersole + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ class GeneratedOrderByFragmentRenderer extends TreeParser; @@ -53,6 +54,13 @@ options { /*package*/ String getRenderedFragment() { return buffer.toString(); } + + /** + * Implementation note: This is just a stub. OrderByFragmentRenderer contains the effective implementation. + */ + protected String renderOrderByElement(String expression, String collation, String order, String nulls) { + throw new UnsupportedOperationException("Concrete ORDER BY renderer should override this method."); + } } orderByFragment @@ -61,32 +69,29 @@ orderByFragment ) ; -sortSpecification +sortSpecification { String sortKeySpec = null; String collSpec = null; String ordSpec = null; String nullOrd = null; } : #( - SORT_SPEC sortKeySpecification (collationSpecification)? (orderingSpecification)? + SORT_SPEC sortKeySpec=sortKeySpecification (collSpec=collationSpecification)? (ordSpec=orderingSpecification)? (nullOrd=nullOrdering)? + { out( renderOrderByElement( sortKeySpec, collSpec, ordSpec, nullOrd ) ); } ) ; -sortKeySpecification - : #(SORT_KEY sortKey) +sortKeySpecification returns [String sortKeyExp = null] + : #(SORT_KEY s:sortKey) { sortKeyExp = #s.getText(); } ; sortKey - : i:IDENT { - out( #i ); - } + : IDENT ; -collationSpecification - : c:COLLATE { - out( " collate " ); - out( c ); - } +collationSpecification returns [String collSpecExp = null] + : c:COLLATE { collSpecExp = "collate " + #c.getText(); } ; -orderingSpecification - : o:ORDER_SPEC { - out( " " ); - out( #o ); - } +orderingSpecification returns [String ordSpecExp = null] + : o:ORDER_SPEC { ordSpecExp = #o.getText(); } + ; + +nullOrdering returns [String nullOrdExp = null] + : n:NULL_ORDER { nullOrdExp = #n.getText(); } ; \ No newline at end of file diff --git a/hibernate-core/src/main/antlr/order-by.g b/hibernate-core/src/main/antlr/order-by.g index dc141d1fe6..33ecbcccf1 100644 --- a/hibernate-core/src/main/antlr/order-by.g +++ b/hibernate-core/src/main/antlr/order-by.g @@ -46,6 +46,7 @@ tokens ORDER_BY; SORT_SPEC; ORDER_SPEC; + NULL_ORDER; SORT_KEY; EXPR_LIST; DOT; @@ -55,6 +56,9 @@ tokens COLLATE="collate"; ASCENDING="asc"; DESCENDING="desc"; + NULLS="nulls"; + FIRST; + LAST; } @@ -76,7 +80,7 @@ tokens * @return The text. */ protected final String extractText(AST ast) { - // for some reason, within AST creation blocks "[]" I am somtimes unable to refer to the AST.getText() method + // for some reason, within AST creation blocks "[]" I am sometimes unable to refer to the AST.getText() method // using #var (the #var is not interpreted as the rule's output AST). return ast.getText(); } @@ -168,7 +172,7 @@ orderByFragment { trace("orderByFragment"); } * the results should be sorted. */ sortSpecification { trace("sortSpecification"); } - : sortKey (collationSpecification)? (orderingSpecification)? { + : sortKey (collationSpecification)? (orderingSpecification)? (nullOrdering)? { #sortSpecification = #( [SORT_SPEC, "{sort specification}"], #sortSpecification ); #sortSpecification = postProcessSortSpecification( #sortSpecification ); } @@ -290,6 +294,30 @@ orderingSpecification! { trace("orderingSpecification"); } } ; +/** + * Recognition rule for what SQL-2003 terms the null ordering; NULLS FIRST or + * NULLS LAST. + */ +nullOrdering! { trace("nullOrdering"); } + : NULLS n:nullPrecedence { + #nullOrdering = #( [NULL_ORDER, extractText( #n )] ); + } + ; + +nullPrecedence { trace("nullPrecedence"); } + : IDENT { + if ( "first".equalsIgnoreCase( #nullPrecedence.getText() ) ) { + #nullPrecedence.setType( FIRST ); + } + else if ( "last".equalsIgnoreCase( #nullPrecedence.getText() ) ) { + #nullPrecedence.setType( LAST ); + } + else { + throw new SemanticException( "Expecting 'first' or 'last', but found '" + #nullPrecedence.getText() + "' as null ordering precedence." ); + } + } + ; + /** * A simple-property-path is an IDENT followed by one or more (DOT IDENT) sequences */ diff --git a/hibernate-core/src/main/antlr/sql-gen.g b/hibernate-core/src/main/antlr/sql-gen.g index 4abb0cfbfc..2bbfec192b 100644 --- a/hibernate-core/src/main/antlr/sql-gen.g +++ b/hibernate-core/src/main/antlr/sql-gen.g @@ -27,8 +27,11 @@ options { /** the buffer resulting SQL statement is written to */ private StringBuilder buf = new StringBuilder(); + private boolean captureExpression = false; + private StringBuilder expr = new StringBuilder(); + protected void out(String s) { - buf.append(s); + getStringBuilder().append( s ); } /** @@ -72,7 +75,7 @@ options { } protected StringBuilder getStringBuilder() { - return buf; + return captureExpression ? expr : buf; } protected void nyi(AST n) { @@ -92,6 +95,27 @@ options { protected void commaBetweenParameters(String comma) { out(comma); } + + protected void captureExpressionStart() { + captureExpression = true; + } + + protected void captureExpressionFinish() { + captureExpression = false; + } + + protected String resetCapture() { + final String expression = expr.toString(); + expr = new StringBuilder(); + return expression; + } + + /** + * Implementation note: This is just a stub. SqlGenerator contains the effective implementation. + */ + protected String renderOrderByElement(String expression, String order, String nulls) { + throw new UnsupportedOperationException("Concrete SQL generator should override this method."); + } } statement @@ -152,9 +176,14 @@ whereClauseExpr | booleanExpr[ false ] ; -orderExprs +orderExprs { String ordExp = null; String ordDir = null; String ordNul = null; } // TODO: remove goofy space before the comma when we don't have to regression test anymore. - : ( expr ) (dir:orderDirection { out(" "); out(dir); })? ( {out(", "); } orderExprs)? + // Dialect is provided a hook to render each ORDER BY element, so the expression is being captured instead of + // printing to the SQL output directly. See Dialect#renderOrderByElement(String, String, String, NullPrecedence). + : { captureExpressionStart(); } ( expr ) { captureExpressionFinish(); ordExp = resetCapture(); } + (dir:orderDirection { ordDir = #dir.getText(); })? (ordNul=nullOrdering)? + { out( renderOrderByElement( ordExp, ordDir, ordNul ) ); } + ( {out(", "); } orderExprs )? ; groupExprs @@ -167,6 +196,15 @@ orderDirection | DESCENDING ; +nullOrdering returns [String nullOrdExp = null] + : NULLS fl:nullPrecedence { nullOrdExp = #fl.getText(); } + ; + +nullPrecedence + : FIRST + | LAST + ; + whereExpr // Expect the filter subtree, followed by the theta join subtree, followed by the HQL condition subtree. // Might need parens around the HQL condition if there is more than one subtree. diff --git a/hibernate-core/src/main/java/org/hibernate/CacheMode.java b/hibernate-core/src/main/java/org/hibernate/CacheMode.java index f8412aef88..7bc9d9ffe1 100755 --- a/hibernate-core/src/main/java/org/hibernate/CacheMode.java +++ b/hibernate-core/src/main/java/org/hibernate/CacheMode.java @@ -82,22 +82,11 @@ public enum CacheMode { return null; } - if ( GET.name().equalsIgnoreCase( setting ) ) { - return CacheMode.GET; + try { + return CacheMode.valueOf( setting.toUpperCase() ); } - if ( IGNORE.name().equalsIgnoreCase( setting ) ) { - return CacheMode.IGNORE; + catch ( IllegalArgumentException e ) { + throw new MappingException( "Unknown Cache Mode: " + setting ); } - if ( NORMAL.name().equalsIgnoreCase( setting ) ) { - return CacheMode.NORMAL; - } - if ( PUT.name().equalsIgnoreCase( setting ) ) { - return CacheMode.PUT; - } - if ( REFRESH.name().equalsIgnoreCase( setting ) ) { - return CacheMode.REFRESH; - } - - throw new MappingException( "Unknown Cache Mode: " + setting ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java b/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java index d2e455fb02..972621a6f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java +++ b/hibernate-core/src/main/java/org/hibernate/ConnectionReleaseMode.java @@ -38,7 +38,7 @@ public enum ConnectionReleaseMode{ * explicitly close all iterators and scrollable results. This mode may * only be used with a JTA datasource. */ - AFTER_STATEMENT("after_statement"), + AFTER_STATEMENT, /** * Indicates that JDBC connections should be released after each transaction @@ -47,18 +47,14 @@ public enum ConnectionReleaseMode{ *

* This is the default mode starting in 3.1; was previously {@link #ON_CLOSE}. */ - AFTER_TRANSACTION("after_transaction"), + AFTER_TRANSACTION, /** * Indicates that connections should only be released when the Session is explicitly closed * or disconnected; this is the legacy (Hibernate2 and pre-3.1) behavior. */ - ON_CLOSE("on_close"); + ON_CLOSE; - private final String name; - ConnectionReleaseMode(String name){ - this.name = name; - } public static ConnectionReleaseMode parse(String name){ return ConnectionReleaseMode.valueOf( name.toUpperCase() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/FlushMode.java b/hibernate-core/src/main/java/org/hibernate/FlushMode.java index 7c223f0731..f3f886f1b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/FlushMode.java +++ b/hibernate-core/src/main/java/org/hibernate/FlushMode.java @@ -89,22 +89,11 @@ public enum FlushMode { return null; } - if ( AUTO.name().equalsIgnoreCase( setting ) ) { - return FlushMode.AUTO; + try { + return FlushMode.valueOf( setting.toUpperCase() ); } - if ( COMMIT.name().equalsIgnoreCase( setting ) ) { - return FlushMode.COMMIT; + catch ( IllegalArgumentException e ) { + throw new MappingException( "unknown FlushMode : " + setting ); } - if ( NEVER.name().equalsIgnoreCase( setting ) ) { - return FlushMode.NEVER; - } - if ( MANUAL.name().equalsIgnoreCase( setting ) ) { - return FlushMode.MANUAL; - } - if ( ALWAYS.name().equalsIgnoreCase( setting ) ) { - return FlushMode.ALWAYS; - } - - throw new MappingException( "unknown FlushMode : " + setting ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/LockMode.java b/hibernate-core/src/main/java/org/hibernate/LockMode.java index 4895986be1..cde6b6bdf0 100644 --- a/hibernate-core/src/main/java/org/hibernate/LockMode.java +++ b/hibernate-core/src/main/java/org/hibernate/LockMode.java @@ -66,6 +66,15 @@ public enum LockMode { * UPGRADE. */ UPGRADE_NOWAIT( 10 ), + + /** + * Attempt to obtain an upgrade lock, using an Oracle-style + * select for update skip locked. The semantics of + * this lock mode, once obtained, are the same as + * UPGRADE. + */ + UPGRADE_SKIPLOCKED( 10 ), + /** * A WRITE lock is obtained when an object is updated * or inserted. This lock mode is for internal use only and is diff --git a/hibernate-core/src/main/java/org/hibernate/LockOptions.java b/hibernate-core/src/main/java/org/hibernate/LockOptions.java index f156feadf2..0e6e9f48a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/LockOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/LockOptions.java @@ -64,6 +64,12 @@ public class LockOptions implements Serializable { */ public static final int WAIT_FOREVER = -1; + /** + * Indicates that rows that are already locked should be skipped. + * @see #getTimeOut() + */ + public static final int SKIP_LOCKED = -2; + private LockMode lockMode = LockMode.NONE; private int timeout = WAIT_FOREVER; @@ -221,9 +227,9 @@ public class LockOptions implements Serializable { * The timeout is the amount of time, in milliseconds, we should instruct the database * to wait for any requested pessimistic lock acquisition. *

- * {@link #NO_WAIT} and {@link #WAIT_FOREVER} represent 2 "magic" values. + * {@link #NO_WAIT}, {@link #WAIT_FOREVER} or {@link #SKIP_LOCKED} represent 3 "magic" values. * - * @return timeout in milliseconds, or {@link #NO_WAIT} or {@link #WAIT_FOREVER} + * @return timeout in milliseconds, {@link #NO_WAIT}, {@link #WAIT_FOREVER} or {@link #SKIP_LOCKED} */ public int getTimeOut() { return timeout; diff --git a/hibernate-core/src/main/java/org/hibernate/NullPrecedence.java b/hibernate-core/src/main/java/org/hibernate/NullPrecedence.java new file mode 100644 index 0000000000..f4b9dab727 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/NullPrecedence.java @@ -0,0 +1,41 @@ +package org.hibernate; + +/** + * Defines precedence of null values within {@code ORDER BY} clause. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public enum NullPrecedence { + /** + * Null precedence not specified. Relies on the RDBMS implementation. + */ + NONE, + + /** + * Null values appear at the beginning of the sorted collection. + */ + FIRST, + + /** + * Null values appear at the end of the sorted collection. + */ + LAST; + + public static NullPrecedence parse(String type) { + if ( "none".equalsIgnoreCase( type ) ) { + return NullPrecedence.NONE; + } + else if ( "first".equalsIgnoreCase( type ) ) { + return NullPrecedence.FIRST; + } + else if ( "last".equalsIgnoreCase( type ) ) { + return NullPrecedence.LAST; + } + return null; + } + + public static NullPrecedence parse(String type, NullPrecedence defaultValue) { + final NullPrecedence value = parse( type ); + return value != null ? value : defaultValue; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index dca4db0992..5acdb50533 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -266,7 +266,10 @@ public interface Session extends SharedSessionContract { * not be synchronized with the database. This operation cascades to associated * instances if the association is mapped with cascade="evict". * - * @param object a persistent instance + * @param object The entity to evict + * + * @throws NullPointerException if the passed object is {@code null} + * @throws IllegalArgumentException if the passed object is not defined as an entity */ public void evict(Object object); diff --git a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java index 92935d3dad..888fcb460e 100644 --- a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java @@ -27,6 +27,7 @@ import java.io.Serializable; import java.sql.Connection; import java.util.Map; import java.util.Set; + import javax.naming.Referenceable; import org.hibernate.boot.registry.StandardServiceRegistry; diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index b962573da5..cc3692194e 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -25,6 +25,8 @@ package org.hibernate; import java.io.Serializable; +import org.hibernate.procedure.ProcedureCall; + /** * Contract methods shared between {@link Session} and {@link StatelessSession} * @@ -91,17 +93,18 @@ public interface SharedSessionContract extends Serializable { * * @return The representation of the procedure call. */ - public StoredProcedureCall createStoredProcedureCall(String procedureName); + public ProcedureCall createStoredProcedureCall(String procedureName); /** - * Creates a call to a stored procedure with specific result set entity mappings + * Creates a call to a stored procedure with specific result set entity mappings. Each class named + * is considered a "root return". * * @param procedureName The name of the procedure. * @param resultClasses The entity(s) to map the result on to. * * @return The representation of the procedure call. */ - public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses); + public ProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses); /** * Creates a call to a stored procedure with specific result set entity mappings @@ -111,7 +114,7 @@ public interface SharedSessionContract extends Serializable { * * @return The representation of the procedure call. */ - public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings); + public ProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings); /** * Create {@link Criteria} instance for the given class (entity or subclasses/implementors) diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureCall.java b/hibernate-core/src/main/java/org/hibernate/StoredProcedureCall.java deleted file mode 100644 index 41fa99f58e..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/StoredProcedureCall.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2012, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate; - -import java.util.List; -import javax.persistence.ParameterMode; -import javax.persistence.TemporalType; - -import org.hibernate.type.Type; - -/** - * @author Steve Ebersole - */ -public interface StoredProcedureCall extends BasicQueryContract, SynchronizeableQuery { - @Override - StoredProcedureCall addSynchronizedQuerySpace(String querySpace); - - @Override - StoredProcedureCall addSynchronizedEntityName(String entityName) throws MappingException; - - @Override - StoredProcedureCall addSynchronizedEntityClass(Class entityClass) throws MappingException; - - /** - * Get the name of the stored procedure to be called. - * - * @return The procedure name. - */ - public String getProcedureName(); - - - /** - * Register a positional parameter. - * All positional parameters must be registered. - * - * @param position parameter position - * @param type type of the parameter - * @param mode parameter mode - * - * @return the same query instance - */ - StoredProcedureCall registerStoredProcedureParameter( - int position, - Class type, - ParameterMode mode); - - /** - * Register a named parameter. - * When using parameter names, all parameters must be registered - * in the order in which they occur in the parameter list of the - * stored procedure. - * - * @param parameterName name of the parameter as registered or - *

- * specified in metadata - * @param type type of the parameter - * @param mode parameter mode - * - * @return the same query instance - */ - StoredProcedureCall registerStoredProcedureParameter( - String parameterName, - Class type, - ParameterMode mode); - - /** - * Retrieve all registered parameters. - * - * @return The (immutable) list of all registered parameters. - */ - public List getRegisteredParameters(); - - /** - * Retrieve parameter registered by name. - * - * @param name The name under which the parameter of interest was registered. - * - * @return The registered parameter. - */ - public StoredProcedureParameter getRegisteredParameter(String name); - public StoredProcedureParameter getRegisteredParameter(int position); - - public StoredProcedureOutputs getOutputs(); - - /** - * Describes a parameter registered with the stored procedure. Parameters can be either named or positional - * as the registration mechanism. Named and positional should not be mixed. - */ - public static interface StoredProcedureParameter { - /** - * The name under which this parameter was registered. Can be {@code null} which should indicate that - * positional registration was used (and therefore {@link #getPosition()} should return non-null. - * - * @return The name; - */ - public String getName(); - - /** - * The position at which this parameter was registered. Can be {@code null} which should indicate that - * named registration was used (and therefore {@link #getName()} should return non-null. - * - * @return The name; - */ - public Integer getPosition(); - - /** - * Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType} - * is called explicitly). - * - * @return The parameter Java type. - */ - public Class getType(); - - /** - * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure - * definition (is it an INPUT parameter? An OUTPUT parameter? etc). - * - * @return The parameter mode. - */ - public ParameterMode getMode(); - - /** - * Set the Hibernate mapping type for this parameter. - * - * @param type The Hibernate mapping type. - */ - public void setHibernateType(Type type); - - /** - * Retrieve the binding associated with this parameter. The binding is only relevant for INPUT parameters. Can - * return {@code null} if nothing has been bound yet. To bind a value to the parameter use one of the - * {@link #bindValue} methods. - * - * @return The parameter binding - */ - public StoredProcedureParameterBind getParameterBind(); - - /** - * Bind a value to the parameter. How this value is bound to the underlying JDBC CallableStatement is - * totally dependent on the Hibernate type. - * - * @param value The value to bind. - */ - public void bindValue(T value); - - /** - * Bind a value to the parameter, using just a specified portion of the DATE/TIME value. It is illegal to call - * this form if the parameter is not DATE/TIME type. The Hibernate type is circumvented in this case and - * an appropriate "precision" Type is used instead. - * - * @param value The value to bind - * @param explicitTemporalType An explicitly supplied TemporalType. - */ - public void bindValue(T value, TemporalType explicitTemporalType); - } - - /** - * Describes an input value binding for any IN/INOUT parameters. - */ - public static interface StoredProcedureParameterBind { - /** - * Retrieves the bound value. - * - * @return The bound value. - */ - public T getValue(); - - /** - * If {@code } represents a DATE/TIME type value, JPA usually allows specifying the particular parts of - * the DATE/TIME value to be bound. This value represents the particular part the user requested to be bound. - * - * @return The explicitly supplied TemporalType. - */ - public TemporalType getExplicitTemporalType(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java index 36b1a710a3..612ffd55ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java @@ -72,7 +72,9 @@ public abstract class AbstractEntityInsertAction extends EntityAction { this.isExecuted = false; this.areTransientReferencesNullified = false; - handleNaturalIdPreSaveNotifications(); + if (id != null) { + handleNaturalIdPreSaveNotifications(); + } } /** @@ -193,7 +195,17 @@ public abstract class AbstractEntityInsertAction extends EntityAction { /** * Handle sending notifications needed for natural-id after saving */ - protected void handleNaturalIdPostSaveNotifications() { + public void handleNaturalIdPostSaveNotifications(Serializable generatedId) { + if (isEarlyInsert()) { + // with early insert, we still need to add a local (transactional) natural id cross-reference + getSession().getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference( + getPersister(), + generatedId, + state, + null, + CachedNaturalIdValueSource.INSERT + ); + } // after save, we need to manage the shared cache entries getSession().getPersistenceContext().getNaturalIdHelper().manageSharedNaturalIdCrossReference( getPersister(), diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java index 837a947087..ea645fcdc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java @@ -123,7 +123,7 @@ public final class EntityInsertAction extends AbstractEntityInsertAction { } } - handleNaturalIdPostSaveNotifications(); + handleNaturalIdPostSaveNotifications(id); postInsert(); diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Index.java b/hibernate-core/src/main/java/org/hibernate/annotations/Index.java index 7c5df2e731..6779606255 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Index.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Index.java @@ -33,9 +33,11 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * Define a DB index * * @author Emmanuel Bernard + * @deprecated Using {@link javax.persistence.Index} instead. */ @Target({FIELD, METHOD}) @Retention(RUNTIME) +@Deprecated public @interface Index { String name(); diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Nationalized.java b/hibernate-core/src/main/java/org/hibernate/annotations/Nationalized.java new file mode 100644 index 0000000000..84c468d522 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Nationalized.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Marks a character data type (String, Character, character, Clob) as being a nationalized variant + * (NVARCHAR, NCHAR, NCLOB, etc). + * + * @author Steve Ebersole + */ +@Target( { METHOD, FIELD } ) +@Retention( RUNTIME ) +public @interface Nationalized { +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java index eb7685f634..c58bce490b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java @@ -26,19 +26,26 @@ package org.hibernate.boot.registry; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl; import org.hibernate.boot.registry.selector.Availability; import org.hibernate.boot.registry.selector.AvailabilityAnnouncer; +import org.hibernate.boot.registry.selector.internal.StrategySelectorBuilder; import org.hibernate.integrator.internal.IntegratorServiceImpl; import org.hibernate.integrator.spi.Integrator; -import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; -import org.hibernate.boot.registry.selector.internal.StrategySelectorBuilder; +import org.hibernate.internal.util.ClassLoaderHelper; /** * Builder for bootstrap {@link org.hibernate.service.ServiceRegistry} instances. * * @author Steve Ebersole + * @author Brett Meyer * * @see BootstrapServiceRegistryImpl * @see StandardServiceRegistryBuilder#StandardServiceRegistryBuilder(org.hibernate.boot.registry.BootstrapServiceRegistry) @@ -46,8 +53,10 @@ import org.hibernate.boot.registry.selector.internal.StrategySelectorBuilder; public class BootstrapServiceRegistryBuilder { private final LinkedHashSet providedIntegrators = new LinkedHashSet(); private List providedClassLoaders; - + private ClassLoaderService providedClassLoaderService; private StrategySelectorBuilder strategySelectorBuilder = new StrategySelectorBuilder(); + + /** * Add an {@link Integrator} to be applied to the bootstrap registry. @@ -75,6 +84,18 @@ public class BootstrapServiceRegistryBuilder { return this; } + /** + * Adds a provided {@link ClassLoaderService} for use in class-loading and resource-lookup + * + * @param classLoader The class loader to use + * + * @return {@code this}, for method chaining + */ + public BootstrapServiceRegistryBuilder with(ClassLoaderService classLoaderService) { + providedClassLoaderService = classLoaderService; + return this; + } + /** * Applies the specified {@link ClassLoader} as the application class loader for the bootstrap registry * @@ -171,7 +192,23 @@ public class BootstrapServiceRegistryBuilder { * @return The built bootstrap registry */ public BootstrapServiceRegistry build() { - final ClassLoaderServiceImpl classLoaderService = new ClassLoaderServiceImpl( providedClassLoaders ); + final ClassLoaderService classLoaderService; + if ( providedClassLoaderService == null ) { + // Use a set. As an example, in JPA, OsgiClassLoader may be in both + // the providedClassLoaders and the overridenClassLoader. + final Set classLoaders = new HashSet(); + + if ( providedClassLoaders != null ) { + classLoaders.addAll( providedClassLoaders ); + } + if ( ClassLoaderHelper.overridenClassLoader != null ) { + classLoaders.add( ClassLoaderHelper.overridenClassLoader ); + } + + classLoaderService = new ClassLoaderServiceImpl( classLoaders ); + } else { + classLoaderService = providedClassLoaderService; + } final IntegratorServiceImpl integratorService = new IntegratorServiceImpl( providedIntegrators, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java index 7d9eeeb36e..23a5892f2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java @@ -35,14 +35,13 @@ import org.hibernate.cfg.Environment; import org.hibernate.integrator.spi.Integrator; import org.hibernate.integrator.spi.IntegratorService; import org.hibernate.internal.util.config.ConfigurationHelper; -import org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl; import org.hibernate.jaxb.spi.cfg.JaxbHibernateConfiguration; import org.hibernate.boot.registry.internal.ConfigLoader; +import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.service.Service; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.StandardServiceInitiators; import org.hibernate.service.internal.ProvidedService; -import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.service.spi.ServiceContributor; /** diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java index cc876d1e0e..24cc799660 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java @@ -37,11 +37,10 @@ import java.util.List; import java.util.Map; import java.util.ServiceLoader; -import org.jboss.logging.Logger; - -import org.hibernate.cfg.AvailableSettings; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.cfg.AvailableSettings; +import org.jboss.logging.Logger; /** * Standard implementation of the service for interacting with class loaders @@ -61,7 +60,7 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { this( Collections.singletonList( classLoader ) ); } - public ClassLoaderServiceImpl(List providedClassLoaders) { + public ClassLoaderServiceImpl(Collection providedClassLoaders) { final LinkedHashSet orderedClassLoaderSet = new LinkedHashSet(); // first add all provided class loaders, if any @@ -74,7 +73,7 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { } // normalize adding known class-loaders... - // first the Hibernate class loader + // first, the Hibernate class loader orderedClassLoaderSet.add( ClassLoaderServiceImpl.class.getClassLoader() ); // then the TCCL, if one... final ClassLoader tccl = locateTCCL(); @@ -102,12 +101,20 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { providedClassLoaders.add( classLoader ); } } - addIfSet( providedClassLoaders, AvailableSettings.APP_CLASSLOADER, configVales ); addIfSet( providedClassLoaders, AvailableSettings.RESOURCES_CLASSLOADER, configVales ); addIfSet( providedClassLoaders, AvailableSettings.HIBERNATE_CLASSLOADER, configVales ); addIfSet( providedClassLoaders, AvailableSettings.ENVIRONMENT_CLASSLOADER, configVales ); + if ( providedClassLoaders.isEmpty() ) { + log.debugf( "Incoming config yielded no classloaders; adding standard SE ones" ); + final ClassLoader tccl = locateTCCL(); + if ( tccl != null ) { + providedClassLoaders.add( tccl ); + } + providedClassLoaders.add( ClassLoaderServiceImpl.class.getClassLoader() ); + } + return new ClassLoaderServiceImpl( providedClassLoaders ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index d24b50f3f1..b248afba3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -26,9 +26,7 @@ package org.hibernate.boot.registry.selector.internal; import java.util.ArrayList; import java.util.List; -import org.jboss.logging.Logger; - -import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.selector.Availability; import org.hibernate.boot.registry.selector.AvailabilityAnnouncer; import org.hibernate.boot.registry.selector.SimpleAvailabilityImpl; @@ -97,6 +95,7 @@ import org.hibernate.engine.transaction.spi.TransactionFactory; import org.hibernate.hql.spi.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.PersistentTableBulkIdStrategy; import org.hibernate.hql.spi.TemporaryTableBulkIdStrategy; +import org.jboss.logging.Logger; /** * @author Steve Ebersole @@ -127,7 +126,7 @@ public class StrategySelectorBuilder { explicitAvailabilities.add( availability ); } - public StrategySelector buildSelector(ClassLoaderServiceImpl classLoaderService) { + public StrategySelector buildSelector(ClassLoaderService classLoaderService) { StrategySelectorImpl strategySelector = new StrategySelectorImpl( classLoaderService ); // build the baseline... diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/JdbcProxyException.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/EnhancementException.java similarity index 80% rename from hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/JdbcProxyException.java rename to hibernate-core/src/main/java/org/hibernate/bytecode/enhance/EnhancementException.java index 86767ed6d9..2955cbcec6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/JdbcProxyException.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/EnhancementException.java @@ -21,21 +21,19 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.engine.jdbc.internal.proxy; +package org.hibernate.bytecode.enhance; import org.hibernate.HibernateException; /** - * Indicates a problem defining or instantiating a JDBC proxy class. - * * @author Steve Ebersole */ -public class JdbcProxyException extends HibernateException { - public JdbcProxyException(String message, Throwable root) { - super( message, root ); - } - - public JdbcProxyException(String message) { +public class EnhancementException extends HibernateException { + public EnhancementException(String message) { super( message ); } + + public EnhancementException(String message, Throwable root) { + super( message, root ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java new file mode 100644 index 0000000000..f42c3ae83d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java @@ -0,0 +1,98 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.bytecode.enhance.spi; + +import javassist.CtClass; +import javassist.CtField; + +/** + * todo : not sure its a great idea to expose Javassist classes this way. + * maybe wrap them in our own contracts? + * + * @author Steve Ebersole + */ +public interface EnhancementContext { + /** + * Obtain access to the ClassLoader that can be used to load Class references. In JPA SPI terms, this + * should be a "temporary class loader" as defined by + * {@link javax.persistence.spi.PersistenceUnitInfo#getNewTempClassLoader()} + */ + public ClassLoader getLoadingClassLoader(); + + /** + * Does the given class descriptor represent a entity class? + * + * @param classDescriptor The descriptor of the class to check. + * + * @return {@code true} if the class is an entity; {@code false} otherwise. + */ + public boolean isEntityClass(CtClass classDescriptor); + + /** + * Does the given class name represent an embeddable/component class? + * + * @param classDescriptor The descriptor of the class to check. + * + * @return {@code true} if the class is an embeddable/component; {@code false} otherwise. + */ + public boolean isCompositeClass(CtClass classDescriptor); + + /** + * Should we in-line dirty checking for persistent attributes for this class? + * + * @param classDescriptor The descriptor of the class to check. + * + * @return {@code true} indicates that dirty checking should be in-lined within the entity; {@code false} + * indicates it should not. In-lined is more easily serializable and probably more performant. + */ + public boolean doDirtyCheckingInline(CtClass classDescriptor); + + public boolean hasLazyLoadableAttributes(CtClass classDescriptor); + + // todo : may be better to invert these 2 such that the context is asked for an ordered list of persistent fields for an entity/composite + + /** + * Does the field represent persistent state? Persistent fields will be "enhanced". + *

+ // may be better to perform basic checks in the caller (non-static, etc) and call out with just the + // Class name and field name... + + * @param ctField The field reference. + * + * @return {@code true} if the field is ; {@code false} otherwise. + */ + public boolean isPersistentField(CtField ctField); + + /** + * For fields which are persistent (according to {@link #isPersistentField}), determine the corresponding ordering + * maintained within the Hibernate metamodel. + + * @param persistentFields The persistent field references. + * + * @return The ordered references. + */ + public CtField[] order(CtField[] persistentFields); + + public boolean isLazyLoadable(CtField field); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java new file mode 100644 index 0000000000..4473739e92 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java @@ -0,0 +1,986 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.bytecode.enhance.spi; + +import javax.persistence.Transient; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtField; +import javassist.CtMethod; +import javassist.CtNewMethod; +import javassist.LoaderClassPath; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.FieldInfo; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; +import javassist.bytecode.StackMapTable; +import javassist.bytecode.annotation.Annotation; +import javassist.bytecode.stackmap.MapMaker; + +import org.jboss.logging.Logger; + +import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.EnhancementException; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedComposite; +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.mapping.PersistentClass; + +/** + * @author Steve Ebersole + * @author Jason Greene + */ +public class Enhancer { + private static final CoreMessageLogger log = Logger.getMessageLogger( CoreMessageLogger.class, Enhancer.class.getName() ); + + public static final String PERSISTENT_FIELD_READER_PREFIX = "$$_hibernate_read_"; + public static final String PERSISTENT_FIELD_WRITER_PREFIX = "$$_hibernate_write_"; + + public static final String ENTITY_INSTANCE_GETTER_NAME = "$$_hibernate_getEntityInstance"; + + public static final String ENTITY_ENTRY_FIELD_NAME = "$$_hibernate_entityEntryHolder"; + public static final String ENTITY_ENTRY_GETTER_NAME = "$$_hibernate_getEntityEntry"; + public static final String ENTITY_ENTRY_SETTER_NAME = "$$_hibernate_setEntityEntry"; + + public static final String PREVIOUS_FIELD_NAME = "$$_hibernate_previousManagedEntity"; + public static final String PREVIOUS_GETTER_NAME = "$$_hibernate_getPreviousManagedEntity"; + public static final String PREVIOUS_SETTER_NAME = "$$_hibernate_setPreviousManagedEntity"; + + public static final String NEXT_FIELD_NAME = "$$_hibernate_nextManagedEntity"; + public static final String NEXT_GETTER_NAME = "$$_hibernate_getNextManagedEntity"; + public static final String NEXT_SETTER_NAME = "$$_hibernate_setNextManagedEntity"; + + public static final String INTERCEPTOR_FIELD_NAME = "$$_hibernate_attributeInterceptor"; + public static final String INTERCEPTOR_GETTER_NAME = "$$_hibernate_getInterceptor"; + public static final String INTERCEPTOR_SETTER_NAME = "$$_hibernate_setInterceptor"; + + private final EnhancementContext enhancementContext; + + private final ClassPool classPool; + private final CtClass managedEntityCtClass; + private final CtClass managedCompositeCtClass; + private final CtClass attributeInterceptorCtClass; + private final CtClass attributeInterceptableCtClass; + private final CtClass entityEntryCtClass; + private final CtClass objectCtClass; + + public Enhancer(EnhancementContext enhancementContext) { + this.enhancementContext = enhancementContext; + this.classPool = buildClassPool( enhancementContext ); + + try { + // add ManagedEntity contract + this.managedEntityCtClass = classPool.makeClass( + ManagedEntity.class.getClassLoader().getResourceAsStream( + ManagedEntity.class.getName().replace( '.', '/' ) + ".class" + ) + ); + + // add ManagedComposite contract + this.managedCompositeCtClass = classPool.makeClass( + ManagedComposite.class.getClassLoader().getResourceAsStream( + ManagedComposite.class.getName().replace( '.', '/' ) + ".class" + ) + ); + + // add PersistentAttributeInterceptable contract + this.attributeInterceptableCtClass = classPool.makeClass( + PersistentAttributeInterceptable.class.getClassLoader().getResourceAsStream( + PersistentAttributeInterceptable.class.getName().replace( '.', '/' ) + ".class" + ) + ); + + // add PersistentAttributeInterceptor contract + this.attributeInterceptorCtClass = classPool.makeClass( + PersistentAttributeInterceptor.class.getClassLoader().getResourceAsStream( + PersistentAttributeInterceptor.class.getName().replace( '.', '/' ) + ".class" + ) + ); + + // "add" EntityEntry + this.entityEntryCtClass = classPool.makeClass( EntityEntry.class.getName() ); + } + catch (IOException e) { + throw new EnhancementException( "Could not prepare Javassist ClassPool", e ); + } + + try { + this.objectCtClass = classPool.getCtClass( Object.class.getName() ); + } + catch (NotFoundException e) { + throw new EnhancementException( "Could not prepare Javassist ClassPool", e ); + } + } + + private ClassPool buildClassPool(EnhancementContext enhancementContext) { + ClassPool classPool = new ClassPool( false ); + ClassLoader loadingClassLoader = enhancementContext.getLoadingClassLoader(); + if ( loadingClassLoader != null ) { + classPool.appendClassPath( new LoaderClassPath( loadingClassLoader ) ); + } + return classPool; + } + + /** + * Performs the enhancement. + * + * @param className The name of the class whose bytecode is being enhanced. + * @param originalBytes The class's original (pre-enhancement) byte code + * + * @return The enhanced bytecode. Could be the same as the original bytecode if the original was + * already enhanced or we could not enhance it for some reason. + * + * @throws EnhancementException + */ + public byte[] enhance(String className, byte[] originalBytes) throws EnhancementException { + final CtClass managedCtClass; + try { + managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) ); + } + catch (IOException e) { + log.unableToBuildEnhancementMetamodel( className ); + return originalBytes; + } + + enhance( managedCtClass ); + + DataOutputStream out = null; + try { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + out = new DataOutputStream( byteStream ); + managedCtClass.toBytecode( out ); + return byteStream.toByteArray(); + } + catch (Exception e) { + log.unableToTransformClass( e.getMessage() ); + throw new HibernateException( "Unable to transform class: " + e.getMessage() ); + } + finally { + try { + if ( out != null ) { + out.close(); + } + } + catch (IOException e) { + //swallow + } + } + } + + private void enhance(CtClass managedCtClass) { + final String className = managedCtClass.getName(); + log.debugf( "Enhancing %s", className ); + + // can't effectively enhance interfaces + if ( managedCtClass.isInterface() ) { + log.debug( "skipping enhancement : interface" ); + return; + } + + // skip already enhanced classes + final String[] interfaceNames = managedCtClass.getClassFile2().getInterfaces(); + for ( String interfaceName : interfaceNames ) { + if ( ManagedEntity.class.getName().equals( interfaceName ) + || ManagedComposite.class.getName().equals( interfaceName ) ) { + log.debug( "skipping enhancement : already enhanced" ); + return; + } + } + + if ( enhancementContext.isEntityClass( managedCtClass ) ) { + enhanceAsEntity( managedCtClass ); + } + else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { + enhanceAsComposite( managedCtClass ); + } + else { + log.debug( "skipping enhancement : not entity or composite" ); + } + } + + private void enhanceAsEntity(CtClass managedCtClass) { + // add the ManagedEntity interface + managedCtClass.addInterface( managedEntityCtClass ); + + enhancePersistentAttributes( managedCtClass ); + + addEntityInstanceHandling( managedCtClass ); + addEntityEntryHandling( managedCtClass ); + addLinkedPreviousHandling( managedCtClass ); + addLinkedNextHandling( managedCtClass ); + } + + private void enhanceAsComposite(CtClass managedCtClass) { + enhancePersistentAttributes( managedCtClass ); + } + + private void addEntityInstanceHandling(CtClass managedCtClass) { + // add the ManagedEntity#$$_hibernate_getEntityInstance method + try { + managedCtClass.addMethod( + CtNewMethod.make( + objectCtClass, + ENTITY_INSTANCE_GETTER_NAME, + new CtClass[0], + new CtClass[0], + "{ return this; }", + managedCtClass + ) + ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add EntityEntry getter", + managedCtClass.getName() + ), + e + ); + } + } + + private void addEntityEntryHandling(CtClass managedCtClass) { + addFieldWithGetterAndSetter( + managedCtClass, + entityEntryCtClass, + ENTITY_ENTRY_FIELD_NAME, + ENTITY_ENTRY_GETTER_NAME, + ENTITY_ENTRY_SETTER_NAME + ); + } + + private void addLinkedPreviousHandling(CtClass managedCtClass) { + addFieldWithGetterAndSetter( + managedCtClass, + managedEntityCtClass, + PREVIOUS_FIELD_NAME, + PREVIOUS_GETTER_NAME, + PREVIOUS_SETTER_NAME + ); + } + + private void addLinkedNextHandling(CtClass managedCtClass) { + addFieldWithGetterAndSetter( + managedCtClass, + managedEntityCtClass, + NEXT_FIELD_NAME, + NEXT_GETTER_NAME, + NEXT_SETTER_NAME + ); + } + + private AnnotationsAttribute getVisibleAnnotations(FieldInfo fieldInfo) { + AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) fieldInfo.getAttribute( AnnotationsAttribute.visibleTag ); + if ( annotationsAttribute == null ) { + annotationsAttribute = new AnnotationsAttribute( fieldInfo.getConstPool(), AnnotationsAttribute.visibleTag ); + fieldInfo.addAttribute( annotationsAttribute ); + } + return annotationsAttribute; + } + + private void enhancePersistentAttributes(CtClass managedCtClass) { + addInterceptorHandling( managedCtClass ); + if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + addInLineDirtyHandling( managedCtClass ); + } + + final IdentityHashMap attrDescriptorMap + = new IdentityHashMap(); + + for ( CtField persistentField : collectPersistentFields( managedCtClass ) ) { + attrDescriptorMap.put( + persistentField.getName(), + enhancePersistentAttribute( managedCtClass, persistentField ) + ); + } + + // lastly, find all references to the transformed fields and replace with calls to the added reader/writer + transformFieldAccessesIntoReadsAndWrites( managedCtClass, attrDescriptorMap ); + } + + private PersistentAttributeDescriptor enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) { + try { + final AttributeTypeDescriptor typeDescriptor = resolveAttributeTypeDescriptor( persistentField ); + return new PersistentAttributeDescriptor( + persistentField, + generateFieldReader( managedCtClass, persistentField, typeDescriptor ), + generateFieldWriter( managedCtClass, persistentField, typeDescriptor ), + typeDescriptor + ); + } + catch (Exception e) { + throw new EnhancementException( + String.format( + "Unable to enhance persistent attribute [%s:%s]", + managedCtClass.getName(), + persistentField.getName() + ), + e + ); + } + } + + private CtField[] collectPersistentFields(CtClass managedCtClass) { + // todo : drive this from the Hibernate metamodel instance... + + final List persistentFieldList = new ArrayList(); + for ( CtField ctField : managedCtClass.getDeclaredFields() ) { + // skip static fields + if ( Modifier.isStatic( ctField.getModifiers() ) ) { + continue; + } + // skip fields added by enhancement + if ( ctField.getName().startsWith( "$" ) ) { + continue; + } + if ( enhancementContext.isPersistentField( ctField ) ) { + persistentFieldList.add( ctField ); + } + } + + return enhancementContext.order( persistentFieldList.toArray( new CtField[persistentFieldList.size()]) ); + } + + private void addInterceptorHandling(CtClass managedCtClass) { + // interceptor handling is only needed if either: + // a) in-line dirty checking has *not* been requested + // b) class has lazy-loadable attributes + if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) + && ! enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { + return; + } + + log.debug( "Weaving in PersistentAttributeInterceptable implementation" ); + + + // add in the PersistentAttributeInterceptable contract + managedCtClass.addInterface( attributeInterceptableCtClass ); + + addFieldWithGetterAndSetter( + managedCtClass, + attributeInterceptorCtClass, + INTERCEPTOR_FIELD_NAME, + INTERCEPTOR_GETTER_NAME, + INTERCEPTOR_SETTER_NAME + ); + } + + private void addInLineDirtyHandling(CtClass managedCtClass) { + // todo : implement + } + + private void addFieldWithGetterAndSetter( + CtClass targetClass, + CtClass fieldType, + String fieldName, + String getterName, + String setterName) { + final CtField theField = addField( targetClass, fieldType, fieldName, true ); + addGetter( targetClass, theField, getterName ); + addSetter( targetClass, theField, setterName ); + } + + private CtField addField(CtClass targetClass, CtClass fieldType, String fieldName, boolean makeTransient) { + final ConstPool constPool = targetClass.getClassFile().getConstPool(); + + final CtField theField; + try { + theField = new CtField( fieldType, fieldName, targetClass ); + targetClass.addField( theField ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance class [%s] to add field [%s]", + targetClass.getName(), + fieldName + ), + e + ); + } + + // make that new field (1) private, (2) transient and (3) @Transient + if ( makeTransient ) { + theField.setModifiers( theField.getModifiers() | Modifier.TRANSIENT ); + } + theField.setModifiers( Modifier.setPrivate( theField.getModifiers() ) ); + AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( theField.getFieldInfo() ); + annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) ); + return theField; + } + + private void addGetter(CtClass targetClass, CtField theField, String getterName) { + try { + targetClass.addMethod( CtNewMethod.getter( getterName, theField ) ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add getter method [%s]", + targetClass.getName(), + getterName + ), + e + ); + } + } + + private void addSetter(CtClass targetClass, CtField theField, String setterName) { + try { + targetClass.addMethod( CtNewMethod.setter( setterName, theField ) ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add setter method [%s]", + targetClass.getName(), + setterName + ), + e + ); + } + } + + private CtMethod generateFieldReader( + CtClass managedCtClass, + CtField persistentField, + AttributeTypeDescriptor typeDescriptor) + throws BadBytecode, CannotCompileException { + + final FieldInfo fieldInfo = persistentField.getFieldInfo(); + final String fieldName = fieldInfo.getName(); + final String readerName = PERSISTENT_FIELD_READER_PREFIX + fieldName; + + // read attempts only have to deal lazy-loading support, not dirty checking; so if the field + // is not enabled as lazy-loadable return a plain simple getter as the reader + if ( ! enhancementContext.isLazyLoadable( persistentField ) ) { + // not lazy-loadable... + // EARLY RETURN!!! + try { + CtMethod reader = CtNewMethod.getter( readerName, persistentField ); + managedCtClass.addMethod( reader ); + return reader; + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add field reader method [%s]", + managedCtClass.getName(), + readerName + ), + e + ); + } + } + + // temporary solution... + String methodBody = typeDescriptor.buildReadInterceptionBodyFragment( fieldName ) + + " return this." + fieldName + ";"; + + try { + CtMethod reader = CtNewMethod.make( + Modifier.PRIVATE, + persistentField.getType(), + readerName, + null, + null, + "{" + methodBody + "}", + managedCtClass + ); + managedCtClass.addMethod( reader ); + return reader; + } + catch (Exception e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add field reader method [%s]", + managedCtClass.getName(), + readerName + ), + e + ); + } + } + + private CtMethod generateFieldWriter( + CtClass managedCtClass, + CtField persistentField, + AttributeTypeDescriptor typeDescriptor) { + + final FieldInfo fieldInfo = persistentField.getFieldInfo(); + final String fieldName = fieldInfo.getName(); + final String writerName = PERSISTENT_FIELD_WRITER_PREFIX + fieldName; + + final CtMethod writer; + + try { + if ( ! enhancementContext.isLazyLoadable( persistentField ) ) { + // not lazy-loadable... + writer = CtNewMethod.setter( writerName, persistentField ); + } + else { + String methodBody = typeDescriptor.buildWriteInterceptionBodyFragment( fieldName ); + writer = CtNewMethod.make( + Modifier.PRIVATE, + CtClass.voidType, + writerName, + new CtClass[] { persistentField.getType() }, + null, + "{" + methodBody + "}", + managedCtClass + ); + } + + if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( fieldName ) ); + } + + managedCtClass.addMethod( writer ); + return writer; + } + catch (Exception e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add field writer method [%s]", + managedCtClass.getName(), + writerName + ), + e + ); + } + } + + private void transformFieldAccessesIntoReadsAndWrites( + CtClass managedCtClass, + IdentityHashMap attributeDescriptorMap) { + + final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); + + for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) { + final MethodInfo methodInfo = (MethodInfo) oMethod; + final String methodName = methodInfo.getName(); + + // skip methods added by enhancement + if ( methodName.startsWith( PERSISTENT_FIELD_READER_PREFIX ) + || methodName.startsWith( PERSISTENT_FIELD_WRITER_PREFIX ) + || methodName.equals( ENTITY_INSTANCE_GETTER_NAME ) + || methodName.equals( ENTITY_ENTRY_GETTER_NAME ) + || methodName.equals( ENTITY_ENTRY_SETTER_NAME ) + || methodName.equals( PREVIOUS_GETTER_NAME ) + || methodName.equals( PREVIOUS_SETTER_NAME ) + || methodName.equals( NEXT_GETTER_NAME ) + || methodName.equals( NEXT_SETTER_NAME ) ) { + continue; + } + + final CodeAttribute codeAttr = methodInfo.getCodeAttribute(); + if ( codeAttr == null ) { + // would indicate an abstract method, continue to next method + continue; + } + + try { + CodeIterator itr = codeAttr.iterator(); + while ( itr.hasNext() ) { + int index = itr.next(); + int op = itr.byteAt( index ); + if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) { + continue; + } + + int constIndex = itr.u16bitAt( index+1 ); + + final String fieldName = constPool.getFieldrefName( constIndex ); + final PersistentAttributeDescriptor attributeDescriptor = attributeDescriptorMap.get( fieldName ); + + if ( attributeDescriptor == null ) { + // its not a field we have enhanced for interception, so skip it + continue; + } + + log.tracef( + "Transforming access to field [%s] from method [%s]", + fieldName, + methodName + ); + + if ( op == Opcode.GETFIELD ) { + int read_method_index = constPool.addMethodrefInfo( + constPool.getThisClassInfo(), + attributeDescriptor.getReader().getName(), + attributeDescriptor.getReader().getSignature() + ); + itr.writeByte( Opcode.INVOKESPECIAL, index ); + itr.write16bit( read_method_index, index+1 ); + } + else { + int write_method_index = constPool.addMethodrefInfo( + constPool.getThisClassInfo(), + attributeDescriptor.getWriter().getName(), + attributeDescriptor.getWriter().getSignature() + ); + itr.writeByte( Opcode.INVOKESPECIAL, index ); + itr.write16bit( write_method_index, index+1 ); + } + } + + StackMapTable smt = MapMaker.make( classPool, methodInfo ); + methodInfo.getCodeAttribute().setAttribute(smt); + } + catch (BadBytecode e) { + throw new EnhancementException( + "Unable to perform field access transformation in method : " + methodName, + e + ); + } + } + } + + private static class PersistentAttributeDescriptor { + private final CtField field; + private final CtMethod reader; + private final CtMethod writer; + private final AttributeTypeDescriptor typeDescriptor; + + private PersistentAttributeDescriptor( + CtField field, + CtMethod reader, + CtMethod writer, + AttributeTypeDescriptor typeDescriptor) { + this.field = field; + this.reader = reader; + this.writer = writer; + this.typeDescriptor = typeDescriptor; + } + + public CtField getField() { + return field; + } + + public CtMethod getReader() { + return reader; + } + + public CtMethod getWriter() { + return writer; + } + + public AttributeTypeDescriptor getTypeDescriptor() { + return typeDescriptor; + } + } + + private static interface AttributeTypeDescriptor { + public String buildReadInterceptionBodyFragment(String fieldName); + public String buildWriteInterceptionBodyFragment(String fieldName); + public String buildInLineDirtyCheckingBodyFragment(String fieldName); + } + + private AttributeTypeDescriptor resolveAttributeTypeDescriptor(CtField persistentField) throws NotFoundException { + // for now cheat... we know we only have Object fields + if ( persistentField.getType() == CtClass.booleanType ) { + return BOOLEAN_DESCRIPTOR; + } + else if ( persistentField.getType() == CtClass.byteType ) { + return BYTE_DESCRIPTOR; + } + else if ( persistentField.getType() == CtClass.charType ) { + return CHAR_DESCRIPTOR; + } + else if ( persistentField.getType() == CtClass.shortType ) { + return SHORT_DESCRIPTOR; + } + else if ( persistentField.getType() == CtClass.intType ) { + return INT_DESCRIPTOR; + } + else if ( persistentField.getType() == CtClass.longType ) { + return LONG_DESCRIPTOR; + } + else if ( persistentField.getType() == CtClass.doubleType ) { + return DOUBLE_DESCRIPTOR; + } + else if ( persistentField.getType() == CtClass.floatType ) { + return FLOAT_DESCRIPTOR; + } + else { + return new ObjectAttributeTypeDescriptor( persistentField.getType() ); + } + } + + private static abstract class AbstractAttributeTypeDescriptor implements AttributeTypeDescriptor { + @Override + public String buildInLineDirtyCheckingBodyFragment(String fieldName) { + // for now... + // todo : hook-in in-lined dirty checking + return String.format( + "System.out.println( \"DIRTY CHECK (%1$s) : \" + this.%1$s + \" -> \" + $1 + \" (dirty=\" + (this.%1$s != $1) +\")\" );", + fieldName + ); + } + } + + private static class ObjectAttributeTypeDescriptor extends AbstractAttributeTypeDescriptor { + private final CtClass concreteType; + + private ObjectAttributeTypeDescriptor(CtClass concreteType) { + this.concreteType = concreteType; + } + + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = (%2$s) $$_hibernate_getInterceptor().readObject(this, \"%1$s\", this.%1$s); " + + "}", + fieldName, + concreteType.getName() + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "%2$s localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = (%2$s) $$_hibernate_getInterceptor().writeObject(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName, + concreteType.getName() + ); + } + } + + private static final AttributeTypeDescriptor BOOLEAN_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readBoolean(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "boolean localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeBoolean(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; + + private static final AttributeTypeDescriptor BYTE_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readByte(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "byte localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeByte(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; + + private static final AttributeTypeDescriptor CHAR_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readChar(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "char localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeChar(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; + + private static final AttributeTypeDescriptor SHORT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readShort(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "short localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeShort(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; + + private static final AttributeTypeDescriptor INT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readInt(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "int localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeInt(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; + + private static final AttributeTypeDescriptor LONG_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readLong(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "long localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeLong(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; + + private static final AttributeTypeDescriptor DOUBLE_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readDouble(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "double localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeDouble(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; + + private static final AttributeTypeDescriptor FLOAT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() { + @Override + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( + "if ( $$_hibernate_getInterceptor() != null ) { " + + "this.%1$s = $$_hibernate_getInterceptor().readFloat(this, \"%1$s\", this.%1$s); " + + "}", + fieldName + ); + } + + @Override + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( + "float localVar = $1;" + + "if ( $$_hibernate_getInterceptor() != null ) {" + + "localVar = $$_hibernate_getInterceptor().writeFloat(this, \"%1$s\", this.%1$s, $1);" + + "}" + + "this.%1$s = localVar;", + fieldName + ); + } + }; +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/instrumentation/internal/javassist/FieldInterceptorImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/instrumentation/internal/javassist/FieldInterceptorImpl.java index c593a53e75..4e245b5fdc 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/instrumentation/internal/javassist/FieldInterceptorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/instrumentation/internal/javassist/FieldInterceptorImpl.java @@ -81,8 +81,7 @@ public final class FieldInterceptorImpl extends AbstractFieldInterceptor impleme } public int readInt(Object target, String name, int oldValue) { - return ( ( Integer ) intercept( target, name, Integer.valueOf( oldValue ) ) ) - .intValue(); + return ( ( Integer ) intercept( target, name, Integer.valueOf( oldValue ) ) ); } public long readLong(Object target, String name, long oldValue) { diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCache.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCache.java index b5a81b36e3..51aa2c0889 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCache.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/StandardQueryCache.java @@ -64,7 +64,8 @@ public class StandardQueryCache implements QueryCache { StandardQueryCache.class.getName() ); - private static final boolean tracing = LOG.isTraceEnabled(); + private static final boolean DEBUGGING = LOG.isDebugEnabled(); + private static final boolean TRACING = LOG.isTraceEnabled(); private QueryResultsRegion cacheRegion; private UpdateTimestampsCache updateTimestampsCache; @@ -93,19 +94,18 @@ public class StandardQueryCache implements QueryCache { cacheRegion.evictAll(); } - @SuppressWarnings({ "UnnecessaryBoxing", "unchecked" }) public boolean put( - QueryKey key, - Type[] returnTypes, - List result, - boolean isNaturalKeyLookup, - SessionImplementor session) throws HibernateException { + final QueryKey key, + final Type[] returnTypes, + final List result, + final boolean isNaturalKeyLookup, + final SessionImplementor session) throws HibernateException { if ( isNaturalKeyLookup && result.isEmpty() ) { return false; } long ts = cacheRegion.nextTimestamp(); - LOG.debugf( "Caching query results in region: %s; timestamp=%s", cacheRegion.getName(), ts ); + if ( DEBUGGING ) LOG.debugf( "Caching query results in region: %s; timestamp=%s", cacheRegion.getName(), ts ); List cacheable = new ArrayList( result.size() + 1 ); logCachedResultDetails( key, null, returnTypes, cacheable ); @@ -127,28 +127,28 @@ public class StandardQueryCache implements QueryCache { @SuppressWarnings({ "unchecked" }) public List get( - QueryKey key, - Type[] returnTypes, - boolean isNaturalKeyLookup, - Set spaces, - SessionImplementor session) throws HibernateException { - LOG.debugf( "Checking cached query results in region: %s", cacheRegion.getName() ); + final QueryKey key, + final Type[] returnTypes, + final boolean isNaturalKeyLookup, + final Set spaces, + final SessionImplementor session) throws HibernateException { + if ( DEBUGGING ) LOG.debugf( "Checking cached query results in region: %s", cacheRegion.getName() ); List cacheable = (List) cacheRegion.get( key ); logCachedResultDetails( key, spaces, returnTypes, cacheable ); if ( cacheable == null ) { - LOG.debug( "Query results were not found in cache" ); + if ( DEBUGGING ) LOG.debug( "Query results were not found in cache" ); return null; } Long timestamp = (Long) cacheable.get( 0 ); if ( !isNaturalKeyLookup && !isUpToDate( spaces, timestamp ) ) { - LOG.debug( "Cached query results were not up-to-date" ); + if ( DEBUGGING ) LOG.debug( "Cached query results were not up-to-date" ); return null; } - LOG.debug( "Returning cached query results" ); + if ( DEBUGGING ) LOG.debug( "Returning cached query results" ); final boolean singleResult = returnTypes.length == 1; for ( int i = 1; i < cacheable.size(); i++ ) { if ( singleResult ) { @@ -179,7 +179,7 @@ public class StandardQueryCache implements QueryCache { // the uoe could occur while resolving // associations, leaving the PC in an // inconsistent state - LOG.debug( "Unable to reassemble cached result set" ); + if ( DEBUGGING ) LOG.debug( "Unable to reassemble cached result set" ); cacheRegion.evict( key ); return null; } @@ -189,8 +189,8 @@ public class StandardQueryCache implements QueryCache { return result; } - protected boolean isUpToDate(Set spaces, Long timestamp) { - LOG.debugf( "Checking query spaces are up-to-date: %s", spaces ); + protected boolean isUpToDate(final Set spaces, final Long timestamp) { + if ( DEBUGGING ) LOG.debugf( "Checking query spaces are up-to-date: %s", spaces ); return updateTimestampsCache.isUpToDate( spaces, timestamp ); } @@ -213,7 +213,7 @@ public class StandardQueryCache implements QueryCache { } private static void logCachedResultDetails(QueryKey key, Set querySpaces, Type[] returnTypes, List result) { - if ( !LOG.isTraceEnabled() ) { + if ( !TRACING ) { return; } LOG.trace( "key.hashCode=" + key.hashCode() ); @@ -238,7 +238,7 @@ public class StandardQueryCache implements QueryCache { } private static void logCachedResultRowDetails(Type[] returnTypes, Object result) { - if ( !LOG.isTraceEnabled() ) { + if ( !TRACING ) { return; } logCachedResultRowDetails( @@ -248,7 +248,7 @@ public class StandardQueryCache implements QueryCache { } private static void logCachedResultRowDetails(Type[] returnTypes, Object[] tuple) { - if ( !tracing ) { + if ( !TRACING ) { return; } if ( tuple == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheKey.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheKey.java index 3fa4b5058f..b12222ed06 100755 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheKey.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheKey.java @@ -110,7 +110,7 @@ public class CacheKey implements Serializable { @Override public String toString() { - // Mainly for OSCache + // Used to be required for OSCache return entityOrRoleName + '#' + key.toString();//"CacheKey#" + type.toString(key, sf); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index f8a1b5e826..416b656f9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -553,8 +553,9 @@ public final class AnnotationBinder { String table = ""; //might be no @Table annotation on the annotated class String catalog = ""; List uniqueConstraints = new ArrayList(); + javax.persistence.Table tabAnn = null; if ( clazzToProcess.isAnnotationPresent( javax.persistence.Table.class ) ) { - javax.persistence.Table tabAnn = clazzToProcess.getAnnotation( javax.persistence.Table.class ); + tabAnn = clazzToProcess.getAnnotation( javax.persistence.Table.class ); table = tabAnn.name(); schema = tabAnn.schema(); catalog = tabAnn.catalog(); @@ -708,7 +709,7 @@ public final class AnnotationBinder { //add process complementary Table definition (index & all) entityBinder.processComplementaryTableDefinitions( clazzToProcess.getAnnotation( org.hibernate.annotations.Table.class ) ); entityBinder.processComplementaryTableDefinitions( clazzToProcess.getAnnotation( org.hibernate.annotations.Tables.class ) ); - + entityBinder.processComplementaryTableDefinitions( tabAnn ); } // parse everything discriminator column relevant in case of single table inheritance @@ -1112,7 +1113,7 @@ public final class AnnotationBinder { jcAnn = jcsAnn.value()[colIndex]; inheritanceJoinedColumns[colIndex] = Ejb3JoinColumn.buildJoinColumn( jcAnn, null, superEntity.getIdentifier(), - ( Map ) null, ( PropertyHolder ) null, mappings + null, null, mappings ); } } @@ -1121,7 +1122,7 @@ public final class AnnotationBinder { inheritanceJoinedColumns = new Ejb3JoinColumn[1]; inheritanceJoinedColumns[0] = Ejb3JoinColumn.buildJoinColumn( jcAnn, null, superEntity.getIdentifier(), - ( Map ) null, ( PropertyHolder ) null, mappings + null, null, mappings ); } LOG.trace( "Subclass joined column(s) created" ); @@ -2164,7 +2165,6 @@ public final class AnnotationBinder { JoinColumn[] annInverseJoins; JoinTable assocTable = propertyHolder.getJoinTable( property ); CollectionTable collectionTable = property.getAnnotation( CollectionTable.class ); - if ( assocTable != null || collectionTable != null ) { final String catalog; @@ -2173,6 +2173,8 @@ public final class AnnotationBinder { final UniqueConstraint[] uniqueConstraints; final JoinColumn[] joins; final JoinColumn[] inverseJoins; + final javax.persistence.Index[] jpaIndexes; + //JPA 2 has priority if ( collectionTable != null ) { @@ -2182,6 +2184,7 @@ public final class AnnotationBinder { uniqueConstraints = collectionTable.uniqueConstraints(); joins = collectionTable.joinColumns(); inverseJoins = null; + jpaIndexes = collectionTable.indexes(); } else { catalog = assocTable.catalog(); @@ -2190,10 +2193,13 @@ public final class AnnotationBinder { uniqueConstraints = assocTable.uniqueConstraints(); joins = assocTable.joinColumns(); inverseJoins = assocTable.inverseJoinColumns(); + jpaIndexes = assocTable.indexes(); } collectionBinder.setExplicitAssociationTable( true ); - + if ( jpaIndexes != null && jpaIndexes.length > 0 ) { + associationTableBinder.setJpaIndex( jpaIndexes ); + } if ( !BinderHelper.isEmptyAnnotationValue( schema ) ) { associationTableBinder.setSchema( schema ); } @@ -2204,7 +2210,7 @@ public final class AnnotationBinder { associationTableBinder.setName( tableName ); } associationTableBinder.setUniqueConstraints( uniqueConstraints ); - + associationTableBinder.setJpaIndex( jpaIndexes ); //set check constaint in the second pass annJoins = joins.length == 0 ? null : joins; annInverseJoins = inverseJoins == null || inverseJoins.length == 0 ? null : inverseJoins; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 1d9a35f65f..5bbeff1613 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -282,6 +282,12 @@ public interface AvailableSettings { */ public static final String JTA_PLATFORM = "hibernate.transaction.jta.platform"; + /** + * Names the {@link org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformResolver} implementation to use. + * @since 4.3 + */ + public static final String JTA_PLATFORM_RESOLVER = "hibernate.transaction.jta.platform_resolver"; + /** * The {@link org.hibernate.cache.spi.RegionFactory} implementation class */ @@ -400,6 +406,12 @@ public interface AvailableSettings { */ public static final String ORDER_INSERTS = "hibernate.order_inserts"; + /** + * Default precedence of null values in {@code ORDER BY} clause. Supported options: {@code none} (default), + * {@code first}, {@code last}. + */ + public static final String DEFAULT_NULL_ORDERING = "hibernate.order_by.default_null_ordering"; + /** * The EntityMode in which set the Session opened from the SessionFactory. */ @@ -658,4 +670,22 @@ public interface AvailableSettings { * Default is to not store direct references. */ public static final String USE_DIRECT_REFERENCE_CACHE_ENTRIES = "hibernate.cache.use_reference_entries"; + + /** + * Enable nationalized character support on all string / clob based attribute ( string, char, clob, text etc ). + * + * Default is false. + */ + public static final String USE_NATIONALIZED_CHARACTER_DATA = "hibernate.use_nationalized_character_data"; + + /** + * A transaction can be rolled back by another thread ("tracking by thread") + * -- not the original application. Examples of this include a JTA + * transaction timeout handled by a background reaper thread. The ability + * to handle this situation requires checking the Thread ID every time + * Session is called. This can certainly have performance considerations. + * + * Default is true (enabled). + */ + public static final String JTA_TRACK_BY_THREAD = "hibernate.jta.track_by_thread"; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index 9da1b9507c..67b06d382b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -98,6 +98,7 @@ import org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory; import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.internal.util.ClassLoaderHelper; import org.hibernate.internal.util.ConfigHelper; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.SerializationHelper; @@ -250,6 +251,7 @@ public class Configuration implements Serializable { private Set defaultNamedGenerators; private Map generatorTables; private Map> uniqueConstraintHoldersByTable; + private Map> jpaIndexHoldersByTable; private Map mappedByResolver; private Map propertyRefResolver; private Map anyMetaDefs; @@ -323,6 +325,7 @@ public class Configuration implements Serializable { defaultSqlResultSetMappingNames = new HashSet(); defaultNamedGenerators = new HashSet(); uniqueConstraintHoldersByTable = new HashMap>(); + jpaIndexHoldersByTable = new HashMap>( ); mappedByResolver = new HashMap(); propertyRefResolver = new HashMap(); caches = new ArrayList(); @@ -720,7 +723,7 @@ public class Configuration implements Serializable { */ public Configuration addResource(String resourceName) throws MappingException { LOG.readingMappingsFromResource( resourceName ); - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader contextClassLoader = ClassLoaderHelper.getContextClassLoader(); InputStream resourceInputStream = null; if ( contextClassLoader != null ) { resourceInputStream = contextClassLoader.getResourceAsStream( resourceName ); @@ -876,7 +879,7 @@ public class Configuration implements Serializable { @SuppressWarnings({ "unchecked" }) - private Iterator iterateGenerators(Dialect dialect) throws MappingException { + public Iterator iterateGenerators(Dialect dialect) throws MappingException { TreeMap generators = new TreeMap(); String defaultCatalog = properties.getProperty( Environment.DEFAULT_CATALOG ); @@ -1302,6 +1305,11 @@ public class Configuration implements Serializable { protected void secondPassCompile() throws MappingException { LOG.trace( "Starting secondPassCompile() processing" ); + + // TEMPORARY + // Ensure the correct ClassLoader is used in commons-annotations. + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader( ClassLoaderHelper.getContextClassLoader() ); //process default values first { @@ -1374,11 +1382,25 @@ public class Configuration implements Serializable { for ( UniqueConstraintHolder holder : uniqueConstraints ) { uniqueIndexPerTable++; final String keyName = StringHelper.isEmpty( holder.getName() ) - ? "key" + uniqueIndexPerTable + ? "UK_" + table.getName() + "_" + uniqueIndexPerTable : holder.getName(); buildUniqueKeyFromColumnNames( table, keyName, holder.getColumns() ); } } + + for(Table table : jpaIndexHoldersByTable.keySet()){ + final List jpaIndexHolders = jpaIndexHoldersByTable.get( table ); + int uniqueIndexPerTable = 0; + for ( JPAIndexHolder holder : jpaIndexHolders ) { + uniqueIndexPerTable++; + final String keyName = StringHelper.isEmpty( holder.getName() ) + ? "idx_"+table.getName()+"_" + uniqueIndexPerTable + : holder.getName(); + buildUniqueKeyFromColumnNames( table, keyName, holder.getColumns(), holder.getOrdering(), holder.isUnique() ); + } + } + + Thread.currentThread().setContextClassLoader( tccl ); } private void processSecondPassesOfType(Class type) { @@ -1532,7 +1554,11 @@ public class Configuration implements Serializable { } } - private void buildUniqueKeyFromColumnNames(Table table, String keyName, String[] columnNames) { + private void buildUniqueKeyFromColumnNames(Table table, String keyName, String[] columnNames){ + buildUniqueKeyFromColumnNames( table, keyName, columnNames, null, true ); + } + + private void buildUniqueKeyFromColumnNames(Table table, String keyName, String[] columnNames, String[] orderings, boolean unique) { keyName = normalizer.normalizeIdentifierQuoting( keyName ); int size = columnNames.length; @@ -1540,24 +1566,40 @@ public class Configuration implements Serializable { Set unbound = new HashSet(); Set unboundNoLogical = new HashSet(); for ( int index = 0; index < size; index++ ) { - final String logicalColumnName = normalizer.normalizeIdentifierQuoting( columnNames[index] ); + String column = columnNames[index]; try { - final String columnName = createMappings().getPhysicalColumnName( logicalColumnName, table ); + final String columnName = createMappings().getPhysicalColumnName( column, table ); columns[index] = new Column( columnName ); unbound.add( columns[index] ); //column equals and hashcode is based on column name } catch ( MappingException e ) { - unboundNoLogical.add( new Column( logicalColumnName ) ); + unboundNoLogical.add( new Column( column ) ); } } - UniqueKey uk = table.getOrCreateUniqueKey( keyName ); - for ( Column column : columns ) { - if ( table.containsColumn( column ) ) { - uk.addColumn( column ); - unbound.remove( column ); + if ( unique ) { + UniqueKey uk = table.getOrCreateUniqueKey( keyName ); + for ( int i = 0; i < columns.length; i++ ) { + Column column = columns[i]; + String order = orderings != null ? orderings[i] : null; + if ( table.containsColumn( column ) ) { + uk.addColumn( column, order ); + unbound.remove( column ); + } } } + else { + Index index = table.getOrCreateIndex( keyName ); + for ( int i = 0; i < columns.length; i++ ) { + Column column = columns[i]; + String order = orderings != null ? orderings[i] : null; + if ( table.containsColumn( column ) ) { + index.addColumn( column, order ); + unbound.remove( column ); + } + } + } + if ( unbound.size() > 0 || unboundNoLogical.size() > 0 ) { StringBuilder sb = new StringBuilder( "Unable to create unique key constraint (" ); for ( String columnName : columnNames ) { @@ -1566,10 +1608,10 @@ public class Configuration implements Serializable { sb.setLength( sb.length() - 2 ); sb.append( ") on table " ).append( table.getName() ).append( ": database column " ); for ( Column column : unbound ) { - sb.append( column.getName() ).append( ", " ); + sb.append("'").append( column.getName() ).append( "', " ); } for ( Column column : unboundNoLogical ) { - sb.append( column.getName() ).append( ", " ); + sb.append("'").append( column.getName() ).append( "', " ); } sb.setLength( sb.length() - 2 ); sb.append( " not found. Make sure that you use the correct column name which depends on the naming strategy in use (it may not be the same as the property name in the entity, especially for relational types)" ); @@ -3144,6 +3186,19 @@ public class Configuration implements Serializable { return useNewGeneratorMappings.booleanValue(); } + private Boolean useNationalizedCharacterData; + + @Override + @SuppressWarnings( {"UnnecessaryUnboxing"}) + public boolean useNationalizedCharacterData() { + if ( useNationalizedCharacterData == null ) { + final String booleanName = getConfigurationProperties() + .getProperty( AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA ); + useNationalizedCharacterData = Boolean.valueOf( booleanName ); + } + return useNationalizedCharacterData.booleanValue(); + } + private Boolean forceDiscriminatorInSelectsByDefault; @Override @@ -3294,6 +3349,15 @@ public class Configuration implements Serializable { holderList.addAll( uniqueConstraintHolders ); } + public void addJpaIndexHolders(Table table, List holders) { + List holderList = jpaIndexHoldersByTable.get( table ); + if ( holderList == null ) { + holderList = new ArrayList(); + jpaIndexHoldersByTable.put( table, holderList ); + } + holderList.addAll( holders ); + } + public void addMappedBy(String entityName, String propertyName, String inversePropertyName) { mappedByResolver.put( entityName + "." + propertyName, inversePropertyName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java index c2bd3919c2..270ed3192a 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java @@ -211,7 +211,7 @@ public class Ejb3JoinColumn extends Ejb3Column { if ( actualColumns == null || actualColumns.length == 0 ) { return new Ejb3JoinColumn[] { buildJoinColumn( - (JoinColumn) null, + null, mappedBy, joins, propertyHolder, @@ -356,8 +356,8 @@ public class Ejb3JoinColumn extends Ejb3Column { else { defaultName = mappings.getObjectNameNormalizer().normalizeIdentifierQuoting( defaultName ); return new Ejb3JoinColumn( - (String) null, defaultName, - false, false, true, true, null, (String) null, + null, defaultName, + false, false, true, true, null, null, joins, propertyHolder, null, null, true, mappings ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java index d45c6f11d0..7ba0adf99e 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java @@ -22,10 +22,16 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.cfg; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.StringTokenizer; import org.hibernate.AnnotationException; import org.hibernate.MappingException; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Column; import org.hibernate.mapping.Table; @@ -52,6 +58,7 @@ public class IndexOrUniqueKeySecondPass implements SecondPass { this.unique = false; } + /** * Build an index */ @@ -69,11 +76,11 @@ public class IndexOrUniqueKeySecondPass implements SecondPass { this.mappings = mappings; this.unique = unique; } - + @Override public void doSecondPass(Map persistentClasses) throws MappingException { if ( columns != null ) { - for (String columnName : columns) { - addConstraintToColumn( columnName ); + for ( int i = 0; i < columns.length; i++ ) { + addConstraintToColumn( columns[i] ); } } if ( column != null ) { @@ -82,7 +89,7 @@ public class IndexOrUniqueKeySecondPass implements SecondPass { } } - private void addConstraintToColumn(String columnName) { + private void addConstraintToColumn(final String columnName ) { Column column = table.getColumn( new Column( mappings.getPhysicalColumnName( columnName, table ) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/JPAIndexHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/JPAIndexHolder.java new file mode 100644 index 0000000000..bd5f379e88 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/JPAIndexHolder.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import javax.persistence.Index; + +/** + * @author Strong Liu + */ +public class JPAIndexHolder { + + private final String name; + private final String[] columns; + private final String[] ordering; + private final boolean unique; + + public JPAIndexHolder(Index index) { + StringTokenizer tokenizer = new StringTokenizer( index.columnList(), "," ); + List tmp = new ArrayList(); + while ( tokenizer.hasMoreElements() ) { + tmp.add( tokenizer.nextToken().trim() ); + } + this.name = index.name(); + this.columns = new String[tmp.size()]; + this.ordering = new String[tmp.size()]; + this.unique = index.unique(); + initializeColumns( columns, ordering, tmp ); + } + + public String[] getColumns() { + return columns; + } + + public String getName() { + return name; + } + + public String[] getOrdering() { + return ordering; + } + + public boolean isUnique() { + return unique; + } + + private void initializeColumns(String[] columns, String[] ordering, List list) { + for ( int i = 0, size = list.size(); i < size; i++ ) { + final String description = list.get( i ); + final String tmp = description.toLowerCase(); + if ( tmp.endsWith( " desc" ) ) { + columns[i] = description.substring( 0, description.length() - 5 ); + ordering[i] = "desc"; + } + else if ( tmp.endsWith( " asc" ) ) { + columns[i] = description.substring( 0, description.length() - 4 ); + ordering[i] = "asc"; + } + else { + columns[i] = description; + ordering[i] = null; + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java index 799145014c..64daee0423 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java @@ -720,6 +720,8 @@ public interface Mappings { public void addUniqueConstraintHolders(Table table, List uniqueConstraintHolders); + public void addJpaIndexHolders(Table table, List jpaIndexHolders); + public void addMappedBy(String entityName, String propertyName, String inversePropertyName); public String getFromMappedBy(String entityName, String propertyName); @@ -764,6 +766,14 @@ public interface Mappings { */ public boolean useNewGeneratorMappings(); + /** + * Should we use nationalized variants of character data by default? This is controlled by the + * {@link AvailableSettings#USE_NATIONALIZED_CHARACTER_DATA} setting. + * + * @return {@code true} if nationalized character data should be used by default; {@code false} otherwise. + */ + public boolean useNationalizedCharacterData(); + /** * Return the property annotated with @ToOne and @Id if any. * Null otherwise diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java index f98bf456cd..4986925842 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java @@ -375,6 +375,9 @@ public abstract class ResultSetMappingBinder { else if ( "upgrade-nowait".equals( lockMode ) ) { return LockMode.UPGRADE_NOWAIT; } + else if ( "upgrade-skiplocked".equals( lockMode )) { + return LockMode.UPGRADE_SKIPLOCKED; + } else if ( "write".equals( lockMode ) ) { return LockMode.WRITE; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java index d7cf998b58..5ac8e79910 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java @@ -28,7 +28,9 @@ import java.util.Map; import org.hibernate.ConnectionReleaseMode; import org.hibernate.EntityMode; import org.hibernate.MultiTenancyStrategy; +import org.hibernate.NullPrecedence; import org.hibernate.cache.spi.QueryCacheFactory; +import org.hibernate.cache.spi.RegionFactory; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.hql.spi.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.QueryTranslatorFactory; @@ -79,6 +81,7 @@ public final class Settings { private boolean namedQueryStartupCheckingEnabled; private EntityTuplizerFactory entityTuplizerFactory; private boolean checkNullability; + private NullPrecedence defaultNullPrecedence; private boolean initializeLazyStateOutsideTransactions; // private ComponentTuplizerFactory componentTuplizerFactory; todo : HHH-3517 and HHH-1907 // private BytecodeProvider bytecodeProvider; @@ -90,6 +93,8 @@ public final class Settings { private MultiTableBulkIdStrategy multiTableBulkIdStrategy; private BatchFetchStyle batchFetchStyle; private boolean directReferenceCacheEntriesEnabled; + + private boolean jtaTrackByThread; /** @@ -256,6 +261,10 @@ public final class Settings { return entityTuplizerFactory; } + public NullPrecedence getDefaultNullPrecedence() { + return defaultNullPrecedence; + } + // package protected setters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void setDefaultSchemaName(String string) { @@ -451,4 +460,16 @@ public final class Settings { public void setDirectReferenceCacheEntriesEnabled(boolean directReferenceCacheEntriesEnabled) { this.directReferenceCacheEntriesEnabled = directReferenceCacheEntriesEnabled; } + + void setDefaultNullPrecedence(NullPrecedence defaultNullPrecedence) { + this.defaultNullPrecedence = defaultNullPrecedence; + } + + public boolean isJtaTrackByThread() { + return jtaTrackByThread; + } + + public void setJtaTrackByThread(boolean jtaTrackByThread) { + this.jtaTrackByThread = jtaTrackByThread; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java b/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java index dd84af1d44..508d27abd8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java @@ -32,6 +32,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.MultiTenancyStrategy; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.NullPrecedence; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cache.internal.NoCachingRegionFactory; import org.hibernate.cache.internal.RegionFactoryInitiator; @@ -247,6 +248,14 @@ public class SettingsFactory implements Serializable { } settings.setOrderInsertsEnabled( orderInserts ); + String defaultNullPrecedence = ConfigurationHelper.getString( + AvailableSettings.DEFAULT_NULL_ORDERING, properties, "none", "first", "last" + ); + if ( debugEnabled ) { + LOG.debugf( "Default null ordering: %s", defaultNullPrecedence ); + } + settings.setDefaultNullPrecedence( NullPrecedence.parse( defaultNullPrecedence ) ); + //Query parser settings: settings.setQueryTranslatorFactory( createQueryTranslatorFactory( properties, serviceRegistry ) ); @@ -365,6 +374,16 @@ public class SettingsFactory implements Serializable { } settings.setInitializeLazyStateOutsideTransactions( initializeLazyStateOutsideTransactionsEnabled ); + boolean jtaTrackByThread = ConfigurationHelper.getBoolean( + AvailableSettings.JTA_TRACK_BY_THREAD, + properties, + true + ); + if ( debugEnabled ) { + LOG.debugf( "JTA Track by Thread: %s", enabledDisabled(jtaTrackByThread) ); + } + settings.setJtaTrackByThread( jtaTrackByThread ); + return settings; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java index aff4e89a30..1d6c4d9423 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java @@ -762,7 +762,11 @@ public class EntityBinder { null ); - //no check constraints available on joins + if ( secondaryTable != null ) { + TableBinder.addIndexes( table, secondaryTable.indexes(), mappings ); + } + + //no check constraints available on joins join.setTable( table ); //somehow keep joins() for later. @@ -881,7 +885,10 @@ public class EntityBinder { public void setIgnoreIdAnnotations(boolean ignoreIdAnnotations) { this.ignoreIdAnnotations = ignoreIdAnnotations; } - + public void processComplementaryTableDefinitions(javax.persistence.Table table) { + if ( table == null ) return; + TableBinder.addIndexes( persistentClass.getTable(), table.indexes(), mappings ); + } public void processComplementaryTableDefinitions(org.hibernate.annotations.Table table) { //comment and index are processed here if ( table == null ) return; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapKeyJoinColumnDelegator.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapKeyJoinColumnDelegator.java index 2d7584d879..66c77aef46 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapKeyJoinColumnDelegator.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapKeyJoinColumnDelegator.java @@ -22,8 +22,10 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.cfg.annotations; + import java.lang.annotation.Annotation; import javax.persistence.Column; +import javax.persistence.ForeignKey; import javax.persistence.JoinColumn; import javax.persistence.MapKeyJoinColumn; @@ -38,38 +40,52 @@ public class MapKeyJoinColumnDelegator implements JoinColumn { this.column = column; } + @Override public String name() { return column.name(); } + @Override public String referencedColumnName() { return column.referencedColumnName(); } + @Override public boolean unique() { return column.unique(); } + @Override public boolean nullable() { return column.nullable(); } + @Override public boolean insertable() { return column.insertable(); } + @Override public boolean updatable() { return column.updatable(); } + @Override public String columnDefinition() { return column.columnDefinition(); } + @Override public String table() { return column.table(); } + @Override + public ForeignKey foreignKey() { + return column.foreignKey(); + } + + @Override public Class annotationType() { return Column.class; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java index 77f717c279..a06d5a9e86 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java @@ -114,26 +114,27 @@ public abstract class QueryBinder { if ( BinderHelper.isEmptyAnnotationValue( queryAnn.name() ) ) { throw new AnnotationException( "A named query must have a name when used in class or package level" ); } - NamedSQLQueryDefinition query; String resultSetMapping = queryAnn.resultSetMapping(); QueryHint[] hints = queryAnn.hints(); String queryName = queryAnn.query(); + + NamedSQLQueryDefinitionBuilder builder = new NamedSQLQueryDefinitionBuilder( queryAnn.name() ) + .setQuery( queryName ) + .setQuerySpaces( null ) + .setCacheable( getBoolean( queryName, "org.hibernate.cacheable", hints ) ) + .setCacheRegion( getString( queryName, "org.hibernate.cacheRegion", hints ) ) + .setTimeout( getTimeout( queryName, hints ) ) + .setFetchSize( getInteger( queryName, "org.hibernate.fetchSize", hints ) ) + .setFlushMode( getFlushMode( queryName, hints ) ) + .setCacheMode( getCacheMode( queryName, hints ) ) + .setReadOnly( getBoolean( queryName, "org.hibernate.readOnly", hints ) ) + .setComment( getString( queryName, "org.hibernate.comment", hints ) ) + .setParameterTypes( null ) + .setCallable( getBoolean( queryName, "org.hibernate.callable", hints ) ); + if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) { //sql result set usage - query = new NamedSQLQueryDefinitionBuilder( queryAnn.name() ) - .setQuery( queryName ) - .setResultSetRef( resultSetMapping ) - .setQuerySpaces( null ) - .setCacheable( getBoolean( queryName, "org.hibernate.cacheable", hints ) ) - .setCacheRegion( getString( queryName, "org.hibernate.cacheRegion", hints ) ) - .setTimeout( getTimeout( queryName, hints ) ) - .setFetchSize( getInteger( queryName, "org.hibernate.fetchSize", hints ) ) - .setFlushMode( getFlushMode( queryName, hints ) ) - .setCacheMode( getCacheMode( queryName, hints ) ) - .setReadOnly( getBoolean( queryName, "org.hibernate.readOnly", hints ) ) - .setComment( getString( queryName, "org.hibernate.comment", hints ) ) - .setParameterTypes( null ) - .setCallable( getBoolean( queryName, "org.hibernate.callable", hints ) ) + builder.setResultSetRef( resultSetMapping ) .createNamedQueryDefinition(); } else if ( !void.class.equals( queryAnn.resultClass() ) ) { @@ -141,25 +142,14 @@ public abstract class QueryBinder { //FIXME should be done in a second pass due to entity name? final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ ); - query = new NamedSQLQueryDefinitionBuilder( queryAnn.name() ) - .setQuery( queryName ) - .setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ) - .setQuerySpaces( null ) - .setCacheable( getBoolean( queryName, "org.hibernate.cacheable", hints ) ) - .setCacheRegion( getString( queryName, "org.hibernate.cacheRegion", hints ) ) - .setTimeout( getTimeout( queryName, hints ) ) - .setFetchSize( getInteger( queryName, "org.hibernate.fetchSize", hints ) ) - .setFlushMode( getFlushMode( queryName, hints ) ) - .setCacheMode( getCacheMode( queryName, hints ) ) - .setReadOnly( getBoolean( queryName, "org.hibernate.readOnly", hints ) ) - .setComment( getString( queryName, "org.hibernate.comment", hints ) ) - .setParameterTypes( null ) - .setCallable( getBoolean( queryName, "org.hibernate.callable", hints ) ) - .createNamedQueryDefinition(); + builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ); } else { - throw new NotYetImplementedException( "Pure native scalar queries are not yet supported" ); + builder.setQueryReturns( new NativeSQLQueryReturn[0] ); } + + NamedSQLQueryDefinition query = builder.createNamedQueryDefinition(); + if ( isDefault ) { mappings.addDefaultSQLQuery( query.getName(), query ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java index a0475a12dc..4df97b1a01 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java @@ -23,10 +23,7 @@ */ package org.hibernate.cfg.annotations; -import org.jboss.logging.Logger; - import org.hibernate.annotations.OrderBy; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; @@ -36,7 +33,6 @@ import org.hibernate.mapping.PersistentClass; * @author Matthew Inger */ public class SetBinder extends CollectionBinder { - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, SetBinder.class.getName()); public SetBinder() { } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java index d4b77da49a..06f2f3dccd 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java @@ -45,6 +45,7 @@ import org.jboss.logging.Logger; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; +import org.hibernate.annotations.Nationalized; import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Type; import org.hibernate.annotations.common.reflection.XClass; @@ -64,10 +65,14 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; import org.hibernate.type.CharacterArrayClobType; +import org.hibernate.type.CharacterArrayNClobType; +import org.hibernate.type.CharacterNCharType; import org.hibernate.type.EnumType; import org.hibernate.type.PrimitiveCharacterArrayClobType; +import org.hibernate.type.PrimitiveCharacterArrayNClobType; import org.hibernate.type.SerializableToBlobType; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.StringNVarcharType; import org.hibernate.type.WrappedMaterializedBlobType; import org.hibernate.usertype.DynamicParameterizedType; @@ -159,6 +164,9 @@ public class SimpleValueBinder { typeParameters.clear(); String type = BinderHelper.ANNOTATION_STRING_DEFAULT; + final boolean isNationalized = property.isAnnotationPresent( Nationalized.class ) + || mappings.useNationalizedCharacterData(); + Type annType = property.getAnnotation( Type.class ); if ( annType != null ) { setExplicitType( annType ); @@ -204,19 +212,30 @@ public class SimpleValueBinder { } else if ( property.isAnnotationPresent( Lob.class ) ) { if ( mappings.getReflectionManager().equals( returnedClassOrElement, java.sql.Clob.class ) ) { - type = "clob"; + type = isNationalized + ? StandardBasicTypes.NCLOB.getName() + : StandardBasicTypes.CLOB.getName(); + } + else if ( mappings.getReflectionManager().equals( returnedClassOrElement, java.sql.NClob.class ) ) { + type = StandardBasicTypes.NCLOB.getName(); } else if ( mappings.getReflectionManager().equals( returnedClassOrElement, java.sql.Blob.class ) ) { type = "blob"; } else if ( mappings.getReflectionManager().equals( returnedClassOrElement, String.class ) ) { - type = StandardBasicTypes.MATERIALIZED_CLOB.getName(); + type = isNationalized + ? StandardBasicTypes.MATERIALIZED_NCLOB.getName() + : StandardBasicTypes.MATERIALIZED_CLOB.getName(); } else if ( mappings.getReflectionManager().equals( returnedClassOrElement, Character.class ) && isArray ) { - type = CharacterArrayClobType.class.getName(); + type = isNationalized + ? CharacterArrayNClobType.class.getName() + : CharacterArrayClobType.class.getName(); } else if ( mappings.getReflectionManager().equals( returnedClassOrElement, char.class ) && isArray ) { - type = PrimitiveCharacterArrayClobType.class.getName(); + type = isNationalized + ? PrimitiveCharacterArrayNClobType.class.getName() + : PrimitiveCharacterArrayClobType.class.getName(); } else if ( mappings.getReflectionManager().equals( returnedClassOrElement, Byte.class ) && isArray ) { type = WrappedMaterializedBlobType.class.getName(); @@ -254,6 +273,24 @@ public class SimpleValueBinder { type = EnumType.class.getName(); explicitType = type; } + else if ( isNationalized ) { + if ( mappings.getReflectionManager().equals( returnedClassOrElement, String.class ) ) { + // nvarchar + type = StringNVarcharType.INSTANCE.getName(); + explicitType = type; + } + else if ( mappings.getReflectionManager().equals( returnedClassOrElement, Character.class ) ) { + if ( isArray ) { + // nvarchar + type = StringNVarcharType.INSTANCE.getName(); + } + else { + // nchar + type = CharacterNCharType.INSTANCE.getName(); + } + explicitType = type; + } + } // implicit type will check basic types and Serializable classes if ( columns == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java index 64dfb89c30..27ffba5a29 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java @@ -36,6 +36,7 @@ import org.hibernate.annotations.Index; import org.hibernate.cfg.BinderHelper; import org.hibernate.cfg.Ejb3JoinColumn; import org.hibernate.cfg.IndexOrUniqueKeySecondPass; +import org.hibernate.cfg.JPAIndexHolder; import org.hibernate.cfg.Mappings; import org.hibernate.cfg.NamingStrategy; import org.hibernate.cfg.ObjectNameNormalizer; @@ -80,6 +81,7 @@ public class TableBinder { private String ownerEntity; private String associatedEntity; private boolean isJPA2ElementCollection; + private List jpaIndexHolders; public void setSchema(String schema) { this.schema = schema; @@ -105,6 +107,10 @@ public class TableBinder { this.uniqueConstraints = TableBinder.buildUniqueConstraintHolders( uniqueConstraints ); } + public void setJpaIndex(javax.persistence.Index[] jpaIndex){ + this.jpaIndexHolders = buildJpaIndexHolder( jpaIndex ); + } + public void setConstraints(String constraints) { this.constraints = constraints; } @@ -183,6 +189,7 @@ public class TableBinder { namingStrategyHelper, isAbstract, uniqueConstraints, + jpaIndexHolders, constraints, denormalizedSuperTable, mappings, @@ -190,6 +197,7 @@ public class TableBinder { ); } + private ObjectNameSource buildNameContext(String unquotedOwnerTable, String unquotedAssocTable) { String logicalName = mappings.getNamingStrategy().logicalCollectionTableName( name, @@ -211,10 +219,11 @@ public class TableBinder { ObjectNameNormalizer.NamingStrategyHelper namingStrategyHelper, boolean isAbstract, List uniqueConstraints, + List jpaIndexHolders, String constraints, Table denormalizedSuperTable, Mappings mappings, - String subselect) { + String subselect){ schema = BinderHelper.isEmptyAnnotationValue( schema ) ? mappings.getSchemaName() : schema; catalog = BinderHelper.isEmptyAnnotationValue( catalog ) ? mappings.getCatalogName() : catalog; @@ -244,10 +253,14 @@ public class TableBinder { ); } - if ( uniqueConstraints != null && uniqueConstraints.size() > 0 ) { + if ( CollectionHelper.isNotEmpty( uniqueConstraints ) ) { mappings.addUniqueConstraintHolders( table, uniqueConstraints ); } + if ( CollectionHelper.isNotEmpty( jpaIndexHolders ) ) { + mappings.addJpaIndexHolders( table, jpaIndexHolders ); + } + if ( constraints != null ) table.addCheckConstraint( constraints ); // logicalName is null if we are in the second pass @@ -258,6 +271,23 @@ public class TableBinder { return table; } + + + public static Table buildAndFillTable( + String schema, + String catalog, + ObjectNameSource nameSource, + ObjectNameNormalizer.NamingStrategyHelper namingStrategyHelper, + boolean isAbstract, + List uniqueConstraints, + String constraints, + Table denormalizedSuperTable, + Mappings mappings, + String subselect) { + return buildAndFillTable( schema, catalog, nameSource, namingStrategyHelper, isAbstract, uniqueConstraints, null, constraints + , denormalizedSuperTable, mappings, subselect); + } + /** * @deprecated Use {@link #buildAndFillTable} instead. */ @@ -514,6 +544,18 @@ public class TableBinder { } } + public static void addIndexes(Table hibTable, javax.persistence.Index[] indexes, Mappings mappings) { + mappings.addJpaIndexHolders( hibTable, buildJpaIndexHolder( indexes ) ); + } + + public static List buildJpaIndexHolder(javax.persistence.Index[] indexes){ + List holders = new ArrayList( indexes.length ); + for(javax.persistence.Index index : indexes){ + holders.add( new JPAIndexHolder( index ) ); + } + return holders; + } + /** * @deprecated Use {@link #buildUniqueConstraintHolders} instead */ diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java index 43fe82b1f2..f16bb78eb2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/JPAOverriddenAnnotationReader.java @@ -311,7 +311,7 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { public boolean isAnnotationPresent(Class annotationType) { initAnnotations(); - return (T) annotationsMap.get( annotationType ) != null; + return annotationsMap.containsKey( annotationType ); } public Annotation[] getAnnotations() { @@ -837,12 +837,13 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { } private Cacheable getCacheable(Element element, XMLContext.Default defaults){ - if(element==null)return null; - String attValue = element.attributeValue( "cacheable" ); - if(attValue!=null){ - AnnotationDescriptor ad = new AnnotationDescriptor( Cacheable.class ); - ad.setValue( "value", Boolean.valueOf( attValue ) ); - return AnnotationFactory.create( ad ); + if ( element != null ) { + String attValue = element.attributeValue( "cacheable" ); + if ( attValue != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Cacheable.class ); + ad.setValue( "value", Boolean.valueOf( attValue ) ); + return AnnotationFactory.create( ad ); + } } if ( defaults.canUseJavaAnnotations() ) { return getJavaAnnotation( Cacheable.class ); @@ -2262,6 +2263,7 @@ public class JPAOverriddenAnnotationReader implements AnnotationReader { annotation.setValue( "schema", table.schema() ); annotation.setValue( "catalog", table.catalog() ); annotation.setValue( "uniqueConstraints", table.uniqueConstraints() ); + annotation.setValue( "indexes", table.indexes() ); } } if ( StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ActivationContext.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ActivationContext.java new file mode 100644 index 0000000000..b80054e95f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ActivationContext.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg.beanvalidation; + +import java.util.Set; + +import org.hibernate.cfg.Configuration; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; + +/** + * Defines the context needed to call the {@link TypeSafeActivator} + * + * @author Steve Ebersole + */ +public interface ActivationContext { + /** + * Access the requested validation mode(s). + *

+ * IMPL NOTE : the legacy code allowed multiple mode values to be specified, so that is why it is multi-valued here. + * However, I cannot find any good reasoning why it was defined that way and even JPA states it should be a single + * value. For 4.1 (in maintenance) I think it makes the most sense to not mess with it. Discuss for + * 4.2 and beyond. + * + * @return The requested validation modes + */ + public Set getValidationModes(); + + /** + * Access the Configuration + * + * @return The Hibernate Configuration object + */ + public Configuration getConfiguration(); + + /** + * Access the SessionFactory being built to trigger this BV activation + * + * @return The SessionFactory being built + */ + public SessionFactoryImplementor getSessionFactory(); + + /** + * Access the ServiceRegistry specific to the SessionFactory being built. + * + * @return The SessionFactoryServiceRegistry + */ + public SessionFactoryServiceRegistry getServiceRegistry(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java index 50481383e4..c99d5113fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java @@ -22,7 +22,6 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.cfg.beanvalidation; - import java.util.HashSet; import java.util.Properties; import java.util.Set; @@ -37,6 +36,7 @@ import javax.validation.ValidatorFactory; import org.jboss.logging.Logger; import org.hibernate.EntityMode; +import org.hibernate.cfg.Configuration; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.spi.PreDeleteEvent; import org.hibernate.event.spi.PreDeleteEventListener; @@ -53,13 +53,12 @@ import org.hibernate.persister.entity.EntityPersister; * @author Emmanuel Bernard * @author Hardy Ferentschik */ +//FIXME review exception model public class BeanValidationEventListener implements PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener { - private static final CoreMessageLogger LOG = Logger.getMessageLogger( - CoreMessageLogger.class, - BeanValidationEventListener.class.getName() - ); + private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, + BeanValidationEventListener.class.getName()); private ValidatorFactory factory; private ConcurrentHashMap> associationsPerEntityPersister = @@ -77,19 +76,20 @@ public class BeanValidationEventListener * Constructor used in an environment where validator factory is injected (JPA2). * * @param factory The {@code ValidatorFactory} to use to create {@code Validator} instance(s) - * @param properties Configured properties + * @param properties Configued properties */ public BeanValidationEventListener(ValidatorFactory factory, Properties properties) { init( factory, properties ); } - public void initialize(Properties properties) { + public void initialize(Configuration cfg) { if ( !initialized ) { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); - init( factory, properties ); + Properties props = cfg.getProperties(); + init( factory, props ); } } - @Override + public boolean onPreInsert(PreInsertEvent event) { validate( event.getEntity(), event.getPersister().getEntityMode(), event.getPersister(), @@ -97,7 +97,7 @@ public class BeanValidationEventListener ); return false; } - @Override + public boolean onPreUpdate(PreUpdateEvent event) { validate( event.getEntity(), event.getPersister().getEntityMode(), event.getPersister(), @@ -105,7 +105,7 @@ public class BeanValidationEventListener ); return false; } - @Override + public boolean onPreDelete(PreDeleteEvent event) { validate( event.getEntity(), event.getPersister().getEntityMode(), event.getPersister(), @@ -135,38 +135,34 @@ public class BeanValidationEventListener if ( groups.length > 0 ) { final Set> constraintViolations = validator.validate( object, groups ); if ( constraintViolations.size() > 0 ) { - throw createConstraintViolationException( operation, groups, constraintViolations ); + Set> propagatedViolations = + new HashSet>( constraintViolations.size() ); + Set classNames = new HashSet(); + for ( ConstraintViolation violation : constraintViolations ) { + LOG.trace( violation ); + propagatedViolations.add( violation ); + classNames.add( violation.getLeafBean().getClass().getName() ); + } + StringBuilder builder = new StringBuilder(); + builder.append( "Validation failed for classes " ); + builder.append( classNames ); + builder.append( " during " ); + builder.append( operation.getName() ); + builder.append( " time for groups " ); + builder.append( toString( groups ) ); + builder.append( "\nList of constraint violations:[\n" ); + for (ConstraintViolation violation : constraintViolations) { + builder.append( "\t" ).append( violation.toString() ).append("\n"); + } + builder.append( "]" ); + + throw new ConstraintViolationException( + builder.toString(), propagatedViolations + ); } } } - private ConstraintViolationException createConstraintViolationException(GroupsPerOperation.Operation operation, Class[] groups, Set> constraintViolations) { - Set> propagatedViolations = - new HashSet>( constraintViolations.size() ); - Set classNames = new HashSet(); - for ( ConstraintViolation violation : constraintViolations ) { - LOG.trace( violation ); - propagatedViolations.add( violation ); - classNames.add( violation.getLeafBean().getClass().getName() ); - } - StringBuilder builder = new StringBuilder(); - builder.append( "Validation failed for classes " ); - builder.append( classNames ); - builder.append( " during " ); - builder.append( operation.getName() ); - builder.append( " time for groups " ); - builder.append( toString( groups ) ); - builder.append( "\nList of constraint violations:[\n" ); - for ( ConstraintViolation violation : constraintViolations ) { - builder.append( "\t" ).append( violation.toString() ).append( "\n" ); - } - builder.append( "]" ); - - return new ConstraintViolationException( - builder.toString(), propagatedViolations - ); - } - private String toString(Class[] groups) { StringBuilder toString = new StringBuilder( "[" ); for ( Class group : groups ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java index 032c3f9842..aa72560294 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java @@ -25,22 +25,16 @@ package org.hibernate.cfg.beanvalidation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Properties; import java.util.Set; import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.integrator.spi.Integrator; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.metamodel.spi.MetadataImplementor; import org.hibernate.service.spi.SessionFactoryServiceRegistry; @@ -55,311 +49,236 @@ public class BeanValidationIntegrator implements Integrator { ); public static final String APPLY_CONSTRAINTS = "hibernate.validator.apply_to_ddl"; + public static final String BV_CHECK_CLASS = "javax.validation.Validation"; + public static final String MODE_PROPERTY = "javax.persistence.validation.mode"; - private static final String ACTIVATOR_CLASS = "org.hibernate.cfg.beanvalidation.TypeSafeActivator"; - private static final String DDL_METHOD = "applyDDL"; - private static final String ACTIVATE_METHOD = "activateBeanValidation"; - private static final String ASSERT_VALIDATOR_FACTORY_INSTANCE_METHOD = "assertObjectIsValidatorFactoryInstance"; + private static final String ACTIVATOR_CLASS_NAME = "org.hibernate.cfg.beanvalidation.TypeSafeActivator"; + private static final String VALIDATE_SUPPLIED_FACTORY_METHOD_NAME = "validateSuppliedFactory"; + private static final String ACTIVATE_METHOD_NAME = "activate"; - @Override - // TODO Can be removed once the switch to the new metamodel is complete. See also HHH-7470 (HF) - public void integrate( - Configuration configuration, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - final Set modes = ValidationMode.getModes( configuration.getProperties().get( MODE_PROPERTY ) ); - final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); - final Dialect dialect = serviceRegistry.getService( JdbcServices.class ).getDialect(); - final boolean isBeanValidationAvailable = isBeanValidationOnClasspath( classLoaderService ); - final Class typeSafeActivatorClass = loadTypeSafeActivatorClass( serviceRegistry ); - - applyRelationalConstraints( - modes, - isBeanValidationAvailable, - typeSafeActivatorClass, - configuration, - dialect - - ); - applyHibernateListeners( - modes, - isBeanValidationAvailable, - typeSafeActivatorClass, - sessionFactory, - serviceRegistry - ); + /** + * Used to validate the type of an explicitly passed ValidatorFactory instance + * + * @param object The supposed ValidatorFactory instance + */ + @SuppressWarnings("unchecked") + public static void validateFactory(Object object) { + try { + // this direct usage of ClassLoader should be fine since the classes exist in the same jar + final Class activatorClass = BeanValidationIntegrator.class.getClassLoader().loadClass( ACTIVATOR_CLASS_NAME ); + try { + final Method validateMethod = activatorClass.getMethod( VALIDATE_SUPPLIED_FACTORY_METHOD_NAME, Object.class ); + if ( ! validateMethod.isAccessible() ) { + validateMethod.setAccessible( true ); + } + try { + validateMethod.invoke( null, object ); + } + catch (InvocationTargetException e) { + if ( e.getTargetException() instanceof HibernateException ) { + throw (HibernateException) e.getTargetException(); + } + throw new HibernateException( "Unable to check validity of passed ValidatorFactory", e ); + } + catch (IllegalAccessException e) { + throw new HibernateException( "Unable to check validity of passed ValidatorFactory", e ); + } + } + catch (HibernateException e) { + throw e; + } + catch (Exception e) { + throw new HibernateException( "Could not locate method needed for ValidatorFactory validation", e ); + } + } + catch (HibernateException e) { + throw e; + } + catch (Exception e) { + throw new HibernateException( "Could not locate TypeSafeActivator class", e ); + } } @Override - public void integrate(MetadataImplementor metadata, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { + public void integrate( + final MetadataImplementor metadata, + final SessionFactoryImplementor sessionFactory, + final SessionFactoryServiceRegistry serviceRegistry ) { + // IMPL NOTE : see the comments on ActivationContext.getValidationModes() as to why this is multi-valued... final Set modes = ValidationMode.getModes( - sessionFactory.getProperties() - .get( MODE_PROPERTY ) - ); - final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); - final Dialect dialect = serviceRegistry.getService( JdbcServices.class ).getDialect(); - final boolean isBeanValidationAvailable = isBeanValidationOnClasspath( classLoaderService ); - final Class typeSafeActivatorClass = loadTypeSafeActivatorClass( serviceRegistry ); + serviceRegistry.getService( ConfigurationService.class ) + .getSetting( MODE_PROPERTY )); + if ( modes.size() > 1 ) { + LOG.multipleValidationModes( ValidationMode.loggable( modes ) ); + } + if ( modes.size() == 1 && modes.contains( ValidationMode.NONE ) ) { + // we have nothing to do; just return + return; + } - applyRelationalConstraints( - modes, - isBeanValidationAvailable, - typeSafeActivatorClass, - sessionFactory.getProperties(), - metadata, - dialect - ); - applyHibernateListeners( - modes, - isBeanValidationAvailable, - typeSafeActivatorClass, - sessionFactory, - serviceRegistry - ); + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + + // see if the Bean Validation API is available on the classpath + if ( isBeanValidationApiAvailable( classLoaderService ) ) { + // and if so, call out to the TypeSafeActivator + try { + final Class typeSafeActivatorClass = loadTypeSafeActivatorClass( classLoaderService ); + @SuppressWarnings("unchecked") + final Method activateMethod = typeSafeActivatorClass.getMethod( ACTIVATE_METHOD_NAME, ActivationContext.class ); + final ActivationContext activationContext = new ActivationContext() { + @Override + public Set getValidationModes() { + return modes; + } + + @Override + public Configuration getConfiguration() { + return null; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public SessionFactoryServiceRegistry getServiceRegistry() { + return serviceRegistry; + } + }; + + try { + activateMethod.invoke( null, activationContext ); + } + catch (InvocationTargetException e) { + if ( HibernateException.class.isInstance( e.getTargetException() ) ) { + throw ( (HibernateException) e.getTargetException() ); + } + throw new IntegrationException( "Error activating Bean Validation integration", e.getTargetException() ); + } + catch (Exception e) { + throw new IntegrationException( "Error activating Bean Validation integration", e ); + } + } + catch (NoSuchMethodException e) { + throw new HibernateException( "Unable to locate TypeSafeActivator#activate method", e ); + } + } + else { + // otherwise check the validation modes + // todo : in many ways this duplicates thew checks done on the TypeSafeActivator when a ValidatorFactory could not be obtained + validateMissingBeanValidationApi( modes ); + } + } + + + @Override + public void integrate( + final Configuration configuration, + final SessionFactoryImplementor sessionFactory, + final SessionFactoryServiceRegistry serviceRegistry) { + // IMPL NOTE : see the comments on ActivationContext.getValidationModes() as to why this is multi-valued... + final Set modes = ValidationMode.getModes( configuration.getProperties().get( MODE_PROPERTY ) ); + if ( modes.size() > 1 ) { + LOG.multipleValidationModes( ValidationMode.loggable( modes ) ); + } + if ( modes.size() == 1 && modes.contains( ValidationMode.NONE ) ) { + // we have nothing to do; just return + return; + } + + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + + // see if the Bean Validation API is available on the classpath + if ( isBeanValidationApiAvailable( classLoaderService ) ) { + // and if so, call out to the TypeSafeActivator + try { + final Class typeSafeActivatorClass = loadTypeSafeActivatorClass( classLoaderService ); + @SuppressWarnings("unchecked") + final Method activateMethod = typeSafeActivatorClass.getMethod( ACTIVATE_METHOD_NAME, ActivationContext.class ); + final ActivationContext activationContext = new ActivationContext() { + @Override + public Set getValidationModes() { + return modes; + } + + @Override + public Configuration getConfiguration() { + return configuration; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public SessionFactoryServiceRegistry getServiceRegistry() { + return serviceRegistry; + } + }; + + try { + activateMethod.invoke( null, activationContext ); + } + catch (InvocationTargetException e) { + if ( HibernateException.class.isInstance( e.getTargetException() ) ) { + throw ( (HibernateException) e.getTargetException() ); + } + throw new IntegrationException( "Error activating Bean Validation integration", e.getTargetException() ); + } + catch (Exception e) { + throw new IntegrationException( "Error activating Bean Validation integration", e ); + } + } + catch (NoSuchMethodException e) { + throw new HibernateException( "Unable to locate TypeSafeActivator#activate method", e ); + } + } + else { + // otherwise check the validation modes + // todo : in many ways this duplicates thew checks done on the TypeSafeActivator when a ValidatorFactory could not be obtained + validateMissingBeanValidationApi( modes ); + } + } + + private boolean isBeanValidationApiAvailable(ClassLoaderService classLoaderService) { + try { + classLoaderService.classForName( BV_CHECK_CLASS ); + return true; + } + catch (Exception e) { + return false; + } + } + + /** + * Used to validate the case when the Bean Validation API is not available. + * + * @param modes The requested validation modes. + */ + private void validateMissingBeanValidationApi(Set modes) { + if ( modes.contains( ValidationMode.CALLBACK ) ) { + throw new IntegrationException( "Bean Validation API was not available, but 'callback' validation was requested" ); + } + if ( modes.contains( ValidationMode.DDL ) ) { + throw new IntegrationException( "Bean Validation API was not available, but 'ddl' validation was requested" ); + } + } + + private Class loadTypeSafeActivatorClass(ClassLoaderService classLoaderService) { + try { + return classLoaderService.classForName( ACTIVATOR_CLASS_NAME ); + } + catch (Exception e) { + throw new HibernateException( "Unable to load TypeSafeActivator class", e ); + } } @Override public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { // nothing to do here afaik } - - public static void validateFactory(Object object) { - Class activatorClass; - try { - activatorClass = BeanValidationIntegrator.class.getClassLoader().loadClass( ACTIVATOR_CLASS ); - } - catch ( HibernateException e ) { - throw e; - } - catch ( Exception e ) { - throw new HibernateException( "Could not locate TypeSafeActivator class", e ); - } - - try { - final Method validateMethod = getValidateMethod( activatorClass ); - invokeValidateMethod( object, validateMethod ); - } - catch ( HibernateException e ) { - throw e; - } - catch ( Exception e ) { - throw new HibernateException( "Could not locate method needed for ValidatorFactory validation", e ); - } - } - - private static void invokeValidateMethod(Object object, Method validateMethod) { - try { - validateMethod.invoke( null, object ); - } - catch ( InvocationTargetException e ) { - if ( e.getTargetException() instanceof HibernateException ) { - throw ( HibernateException ) e.getTargetException(); - } - throw new HibernateException( "Unable to check validity of passed ValidatorFactory", e ); - } - catch ( IllegalAccessException e ) { - throw new HibernateException( "Unable to check validity of passed ValidatorFactory", e ); - } - } - - private static Method getValidateMethod(Class activatorClass) throws NoSuchMethodException { - final Method validateMethod = activatorClass.getMethod( - ASSERT_VALIDATOR_FACTORY_INSTANCE_METHOD, - Object.class - ); - if ( !validateMethod.isAccessible() ) { - validateMethod.setAccessible( true ); - } - return validateMethod; - } - - /** - * Try to locate a BV class to see if it is available on the classpath - * - * @param classLoaderService the class loader service - * - * @return {@code true} if the Bean Validation classes are on the classpath, {@code false otherwise} - */ - private boolean isBeanValidationOnClasspath(ClassLoaderService classLoaderService) { - // try to locate a BV class to see if it is available on the classpath - boolean isBeanValidationAvailable; - try { - classLoaderService.classForName( BV_CHECK_CLASS ); - isBeanValidationAvailable = true; - } - catch ( Exception error ) { - isBeanValidationAvailable = false; - } - return isBeanValidationAvailable; - } - - private Class loadTypeSafeActivatorClass(SessionFactoryServiceRegistry serviceRegistry) { - try { - return serviceRegistry.getService( ClassLoaderService.class ).classForName( ACTIVATOR_CLASS ); - } - catch ( Exception e ) { - return null; - } - } - - private void applyRelationalConstraints( - Set modes, - boolean beanValidationAvailable, - Class typeSafeActivatorClass, - Configuration configuration, - Dialect dialect) { - if ( !ConfigurationHelper.getBoolean( APPLY_CONSTRAINTS, configuration.getProperties(), true ) ) { - LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" ); - return; - } - - if ( !( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) { - return; - } - - if ( !beanValidationAvailable ) { - if ( modes.contains( ValidationMode.DDL ) ) { - throw new HibernateException( "Bean Validation not available in the class path but required in " + MODE_PROPERTY ); - } - else if ( modes.contains( ValidationMode.AUTO ) ) { - //nothing to activate - return; - } - } - - try { - Method applyDDLMethod = typeSafeActivatorClass.getMethod( - DDL_METHOD, - Collection.class, - Properties.class, - Dialect.class - ); - try { - applyDDLMethod.invoke( - null, - configuration.createMappings().getClasses().values(), - configuration.getProperties(), - dialect - ); - } - catch ( HibernateException e ) { - throw e; - } - catch ( Exception e ) { - throw new HibernateException( "Error applying BeanValidation relational constraints", e ); - } - } - catch ( HibernateException e ) { - throw e; - } - catch ( Exception e ) { - throw new HibernateException( "Unable to locate TypeSafeActivator#applyDDL method", e ); - } - } - - private void applyRelationalConstraints(Set modes, - boolean beanValidationAvailable, - Class typeSafeActivatorClass, - Properties properties, - MetadataImplementor metadata, - Dialect dialect) { - if ( !ConfigurationHelper.getBoolean( APPLY_CONSTRAINTS, properties, true ) ) { - LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" ); - return; - } - if ( !( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) { - return; - } - if ( !beanValidationAvailable ) { - if ( modes.contains( ValidationMode.DDL ) ) { - throw new HibernateException( "Bean Validation not available in the class path but required in " + MODE_PROPERTY ); - } - if ( modes.contains( ValidationMode.AUTO ) ) { - return; //nothing to activate - } - } - try { - Method applyDDLMethod = typeSafeActivatorClass.getMethod( - DDL_METHOD, - Iterable.class, - Properties.class, - ClassLoaderService.class, - Dialect.class - ); - try { - applyDDLMethod.invoke( - null, - metadata.getEntityBindings(), - properties, - metadata.getServiceRegistry().getService( ClassLoaderService.class ), - dialect - ); - } - catch ( HibernateException error ) { - throw error; - } - catch ( Exception error ) { - throw new HibernateException( "Error applying BeanValidation relational constraints", error ); - } - } - catch ( HibernateException error ) { - throw error; - } - catch ( Exception error ) { - throw new HibernateException( "Unable to locate TypeSafeActivator#applyDDL method", error ); - } - } - - private void applyHibernateListeners(Set modes, - boolean beanValidationAvailable, - Class typeSafeActivatorClass, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - // de-activate not-null tracking at the core level when Bean Validation is present unless the user explicitly - // asks for it - if ( sessionFactory.getProperties().getProperty( Environment.CHECK_NULLABILITY ) == null ) { - sessionFactory.getSettings().setCheckNullability( false ); - } - if ( !( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) { - return; - } - if ( !beanValidationAvailable ) { - if ( modes.contains( ValidationMode.CALLBACK ) ) { - throw new HibernateException( "Bean Validation not available in the class path but required in " + MODE_PROPERTY ); - } - if ( modes.contains( ValidationMode.AUTO ) ) { - return; //nothing to activate - } - } - try { - Method activateMethod = typeSafeActivatorClass.getMethod( - ACTIVATE_METHOD, - EventListenerRegistry.class, - Properties.class - ); - try { - activateMethod.invoke( - null, - serviceRegistry.getService( EventListenerRegistry.class ), - sessionFactory.getProperties() - ); - } - catch ( HibernateException e ) { - throw e; - } - catch ( Exception e ) { - throw new HibernateException( "Error applying BeanValidation relational constraints", e ); - } - } - catch ( HibernateException e ) { - throw e; - } - catch ( Exception e ) { - throw new HibernateException( "Unable to locate TypeSafeActivator#applyDDL method", e ); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/IntegrationException.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/IntegrationException.java new file mode 100644 index 0000000000..782fae2e1c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/IntegrationException.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg.beanvalidation; + +import org.hibernate.HibernateException; + +/** + * Indicates a problem integrating Hibernate and the Bean Validation spec. + * + * @author Steve Ebersole + */ +public class IntegrationException extends HibernateException { + public IntegrationException(String message) { + super( message ); + } + + public IntegrationException(String message, Throwable root) { + super( message, root ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java index 7822e3e2ee..296859ef54 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java @@ -23,119 +23,147 @@ */ package org.hibernate.cfg.beanvalidation; -import java.util.ArrayList; +import javax.validation.Validation; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import javax.validation.metadata.BeanDescriptor; +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.PropertyDescriptor; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; +import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; -import javax.validation.Validation; -import javax.validation.ValidatorFactory; -import javax.validation.metadata.BeanDescriptor; -import javax.validation.metadata.ConstraintDescriptor; -import javax.validation.metadata.PropertyDescriptor; import org.jboss.logging.Logger; import org.hibernate.AssertionFailure; -import org.hibernate.EntityMode; -import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; -import org.hibernate.cfg.beanvalidation.ddl.DigitsSchemaConstraint; -import org.hibernate.cfg.beanvalidation.ddl.LengthSchemaConstraint; -import org.hibernate.cfg.beanvalidation.ddl.MaxSchemaConstraint; -import org.hibernate.cfg.beanvalidation.ddl.MinSchemaConstraint; -import org.hibernate.cfg.beanvalidation.ddl.NotNullSchemaConstraint; -import org.hibernate.cfg.beanvalidation.ddl.SchemaConstraint; -import org.hibernate.cfg.beanvalidation.ddl.SizeSchemaConstraint; +import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; -import org.hibernate.metamodel.spi.binding.AttributeBinding; -import org.hibernate.metamodel.spi.binding.BasicAttributeBinding; -import org.hibernate.metamodel.spi.binding.EntityBinding; -import org.hibernate.metamodel.spi.binding.EntityIdentifier; +import org.hibernate.mapping.SingleTableSubclass; /** * @author Emmanuel Bernard * @author Hardy Ferentschik + * @author Steve Ebersole */ class TypeSafeActivator { - private static final CoreMessageLogger LOG = Logger.getMessageLogger( - CoreMessageLogger.class, - TypeSafeActivator.class.getName() - ); + private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, TypeSafeActivator.class.getName()); private static final String FACTORY_PROPERTY = "javax.persistence.validation.factory"; - private static final List schemaConstraints; - private static final NotNullSchemaConstraint notNullSchemaConstraint = new NotNullSchemaConstraint(); - - static { - schemaConstraints = new ArrayList(); - schemaConstraints.add( new DigitsSchemaConstraint() ); - schemaConstraints.add( new SizeSchemaConstraint() ); - schemaConstraints.add( new MinSchemaConstraint() ); - schemaConstraints.add( new MaxSchemaConstraint() ); - schemaConstraints.add( new LengthSchemaConstraint() ); - } /** - * Verifies that the specified object is an instance of {@code ValidatorFactory}. - *

- * Note:
- * The check is done here to avoid a hard link to Bean Validation - *

+ * Used to validate a supplied ValidatorFactory instance as being castable to ValidatorFactory. * - * @param object the object to check - * - * @see BeanValidationIntegrator#validateFactory(Object) + * @param object The supplied ValidatorFactory instance. */ - @SuppressWarnings({ "UnusedDeclaration" }) - public static void assertObjectIsValidatorFactoryInstance(Object object) { - if ( object == null ) { - throw new IllegalArgumentException( "null cannot be a valid ValidatorFactory" ); - } - - if ( !ValidatorFactory.class.isInstance( object ) ) { - throw new HibernateException( - "Given object was not an instance of " + ValidatorFactory.class.getName() + "[" + object.getClass() - .getName() + "]" + @SuppressWarnings( {"UnusedDeclaration"}) + public static void validateSuppliedFactory(Object object) { + if ( ! ValidatorFactory.class.isInstance( object ) ) { + throw new IntegrationException( + "Given object was not an instance of " + ValidatorFactory.class.getName() + + "[" + object.getClass().getName() + "]" ); } } - @SuppressWarnings({ "UnusedDeclaration" }) - public static void activateBeanValidation(EventListenerRegistry listenerRegistry, Properties properties) { - ValidatorFactory factory = getValidatorFactory( properties ); - BeanValidationEventListener listener = new BeanValidationEventListener( - factory, properties + @SuppressWarnings("UnusedDeclaration") + public static void activate(ActivationContext activationContext) { + final Properties properties = activationContext.getConfiguration().getProperties(); + final ValidatorFactory factory; + try { + factory = getValidatorFactory( properties ); + } + catch (IntegrationException e) { + if ( activationContext.getValidationModes().contains( ValidationMode.CALLBACK ) ) { + throw new IntegrationException( "Bean Validation provider was not available, but 'callback' validation was requested", e ); + } + if ( activationContext.getValidationModes().contains( ValidationMode.DDL ) ) { + throw new IntegrationException( "Bean Validation provider was not available, but 'ddl' validation was requested", e ); + } + + LOG.debug( "Unable to acquire Bean Validation ValidatorFactory, skipping activation" ); + return; + } + + applyRelationalConstraints( factory, activationContext ); + + applyCallbackListeners( factory, activationContext ); + } + + @SuppressWarnings( {"UnusedDeclaration"}) + public static void applyCallbackListeners(ValidatorFactory validatorFactory, ActivationContext activationContext) { + final Set modes = activationContext.getValidationModes(); + if ( ! ( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) { + return; + } + + // de-activate not-null tracking at the core level when Bean Validation is present unless the user explicitly + // asks for it + if ( activationContext.getConfiguration().getProperty( Environment.CHECK_NULLABILITY ) == null ) { + activationContext.getSessionFactory().getSettings().setCheckNullability( false ); + } + + final BeanValidationEventListener listener = new BeanValidationEventListener( + validatorFactory, + activationContext.getConfiguration().getProperties() ); + final EventListenerRegistry listenerRegistry = activationContext.getServiceRegistry() + .getService( EventListenerRegistry.class ); + listenerRegistry.addDuplicationStrategy( DuplicationStrategyImpl.INSTANCE ); listenerRegistry.appendListeners( EventType.PRE_INSERT, listener ); listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener ); listenerRegistry.appendListeners( EventType.PRE_DELETE, listener ); - listener.initialize( properties ); + listener.initialize( activationContext.getConfiguration() ); } - @SuppressWarnings({ "UnusedDeclaration" }) - // see BeanValidationIntegrator#applyRelationalConstraints - public static void applyDDL(Collection persistentClasses, Properties properties, Dialect dialect) { + @SuppressWarnings({"unchecked", "UnusedParameters"}) + private static void applyRelationalConstraints(ValidatorFactory factory, ActivationContext activationContext) { + final Properties properties = activationContext.getConfiguration().getProperties(); + if ( ! ConfigurationHelper.getBoolean( BeanValidationIntegrator.APPLY_CONSTRAINTS, properties, true ) ){ + LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" ); + return; + } + + final Set modes = activationContext.getValidationModes(); + if ( ! ( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) { + return; + } + + applyRelationalConstraints( + activationContext.getConfiguration().createMappings().getClasses().values(), + properties, + activationContext.getServiceRegistry().getService( JdbcServices.class ).getDialect() + ); + } + + @SuppressWarnings( {"UnusedDeclaration"}) + public static void applyRelationalConstraints(Collection persistentClasses, Properties properties, Dialect dialect) { ValidatorFactory factory = getValidatorFactory( properties ); Class[] groupsArray = new GroupsPerOperation( properties ).get( GroupsPerOperation.Operation.DDL ); Set> groups = new HashSet>( Arrays.asList( groupsArray ) ); @@ -143,7 +171,7 @@ class TypeSafeActivator { for ( PersistentClass persistentClass : persistentClasses ) { final String className = persistentClass.getClassName(); - if ( StringHelper.isEmpty( className ) ) { + if ( className == null || className.length() == 0 ) { continue; } Class clazz; @@ -157,34 +185,32 @@ class TypeSafeActivator { try { applyDDL( "", persistentClass, clazz, factory, groups, true, dialect ); } - catch ( Exception e ) { + catch (Exception e) { LOG.unableToApplyConstraints( className, e ); } } } - private static void applyDDL(String prefix, - PersistentClass persistentClass, - Class clazz, - ValidatorFactory factory, - Set> groups, - boolean activateNotNull, - Dialect dialect) { + private static void applyDDL( + String prefix, + PersistentClass persistentClass, + Class clazz, + ValidatorFactory factory, + Set> groups, + boolean activateNotNull, + Dialect dialect) { final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass( clazz ); + //no bean level constraints can be applied, go to the properties + for ( PropertyDescriptor propertyDesc : descriptor.getConstrainedProperties() ) { Property property = findPropertyByName( persistentClass, prefix + propertyDesc.getPropertyName() ); boolean hasNotNull; if ( property != null ) { hasNotNull = applyConstraints( - propertyDesc.getConstraintDescriptors(), - property, - propertyDesc, - groups, - activateNotNull, - dialect + propertyDesc.getConstraintDescriptors(), property, propertyDesc, groups, activateNotNull, dialect ); if ( property.isComposite() && propertyDesc.isCascaded() ) { - Class componentClass = ( ( Component ) property.getValue() ).getComponentClass(); + Class componentClass = ( (Component) property.getValue() ).getComponentClass(); /* * we can apply not null if the upper component let's us activate not null @@ -194,10 +220,7 @@ class TypeSafeActivator { final boolean canSetNotNullOnColumns = activateNotNull && hasNotNull; applyDDL( prefix + propertyDesc.getPropertyName() + ".", - persistentClass, - componentClass, - factory, - groups, + persistentClass, componentClass, factory, groups, canSetNotNullOnColumns, dialect ); @@ -207,35 +230,38 @@ class TypeSafeActivator { } } - private static boolean applyConstraints(Set> constraintDescriptors, - Property property, - PropertyDescriptor propertyDescriptor, - Set> groups, - boolean canApplyNotNull, - Dialect dialect) { + private static boolean applyConstraints( + Set> constraintDescriptors, + Property property, + PropertyDescriptor propertyDesc, + Set> groups, + boolean canApplyNotNull, + Dialect dialect) { boolean hasNotNull = false; - for ( ConstraintDescriptor constraintDescriptor : constraintDescriptors ) { - if ( groups != null && Collections.disjoint( constraintDescriptor.getGroups(), groups ) ) { + for ( ConstraintDescriptor descriptor : constraintDescriptors ) { + if ( groups != null && Collections.disjoint( descriptor.getGroups(), groups ) ) { continue; } if ( canApplyNotNull ) { - hasNotNull = hasNotNull || notNullSchemaConstraint.applyConstraint( - property, - constraintDescriptor, - propertyDescriptor, - dialect - ); + hasNotNull = hasNotNull || applyNotNull( property, descriptor ); } - for ( SchemaConstraint schemaConstraint : schemaConstraints ) { - schemaConstraint.applyConstraint( property, constraintDescriptor, propertyDescriptor, dialect ); - } + // apply bean validation specific constraints + applyDigits( property, descriptor ); + applySize( property, descriptor, propertyDesc ); + applyMin( property, descriptor, dialect ); + applyMax( property, descriptor, dialect ); + + // apply hibernate validator specific constraints - we cannot import any HV specific classes though! + // no need to check explicitly for @Range. @Range is a composed constraint using @Min and @Max which + // will be taken care later + applyLength( property, descriptor, propertyDesc ); // pass an empty set as composing constraints inherit the main constraint and thus are matching already hasNotNull = hasNotNull || applyConstraints( - constraintDescriptor.getComposingConstraints(), - property, propertyDescriptor, null, + descriptor.getComposingConstraints(), + property, propertyDesc, null, canApplyNotNull, dialect ); @@ -243,75 +269,102 @@ class TypeSafeActivator { return hasNotNull; } + private static void applyMin(Property property, ConstraintDescriptor descriptor, Dialect dialect) { + if ( Min.class.equals( descriptor.getAnnotation().annotationType() ) ) { + @SuppressWarnings("unchecked") + ConstraintDescriptor minConstraint = (ConstraintDescriptor) descriptor; + long min = minConstraint.getAnnotation().value(); - @SuppressWarnings({ "UnusedDeclaration" }) - // see BeanValidationIntegrator#applyRelationalConstraints - public static void applyDDL(Iterable bindings, - Properties properties, - ClassLoaderService classLoaderService, - Dialect dialect) { - final ValidatorFactory factory = getValidatorFactory( properties ); - final Class[] groupsArray = new GroupsPerOperation( properties, classLoaderService ).get( GroupsPerOperation.Operation.DDL ); - final Set> groups = new HashSet>( Arrays.asList( groupsArray ) ); + Column col = (Column) property.getColumnIterator().next(); + String checkConstraint = col.getQuotedName(dialect) + ">=" + min; + applySQLCheck( col, checkConstraint ); + } + } - for ( EntityBinding binding : bindings ) { - final String className = binding.getEntity().getClassName(); - if ( binding.getHierarchyDetails().getEntityMode() != EntityMode.POJO ){ - continue; - } - Class clazz; - try { - clazz = classLoaderService.classForName( className ); - } - catch ( ClassLoadingException error ) { - throw new AssertionFailure( "Entity class not found", error ); - } - try { - final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass( clazz ); - for ( PropertyDescriptor propertyDescriptor : descriptor.getConstrainedProperties() ) { - AttributeBinding attributeBinding = binding.locateAttributeBinding( propertyDescriptor.getPropertyName() ); - if ( attributeBinding != null ) { - applyConstraints( propertyDescriptor, groups, attributeBinding, dialect ); + private static void applyMax(Property property, ConstraintDescriptor descriptor, Dialect dialect) { + if ( Max.class.equals( descriptor.getAnnotation().annotationType() ) ) { + @SuppressWarnings("unchecked") + ConstraintDescriptor maxConstraint = (ConstraintDescriptor) descriptor; + long max = maxConstraint.getAnnotation().value(); + Column col = (Column) property.getColumnIterator().next(); + String checkConstraint = col.getQuotedName(dialect) + "<=" + max; + applySQLCheck( col, checkConstraint ); + } + } + + private static void applySQLCheck(Column col, String checkConstraint) { + String existingCheck = col.getCheckConstraint(); + // need to check whether the new check is already part of the existing check, because applyDDL can be called + // multiple times + if ( StringHelper.isNotEmpty( existingCheck ) && !existingCheck.contains( checkConstraint ) ) { + checkConstraint = col.getCheckConstraint() + " AND " + checkConstraint; + } + col.setCheckConstraint( checkConstraint ); + } + + private static boolean applyNotNull(Property property, ConstraintDescriptor descriptor) { + boolean hasNotNull = false; + if ( NotNull.class.equals( descriptor.getAnnotation().annotationType() ) ) { + if ( !( property.getPersistentClass() instanceof SingleTableSubclass ) ) { + //single table should not be forced to null + if ( !property.isComposite() ) { //composite should not add not-null on all columns + @SuppressWarnings( "unchecked" ) + Iterator iter = property.getColumnIterator(); + while ( iter.hasNext() ) { + iter.next().setNullable( false ); + hasNotNull = true; } } } - catch ( Exception error ) { - LOG.unableToApplyConstraints( className, error ); + hasNotNull = true; + } + return hasNotNull; + } + + private static void applyDigits(Property property, ConstraintDescriptor descriptor) { + if ( Digits.class.equals( descriptor.getAnnotation().annotationType() ) ) { + @SuppressWarnings("unchecked") + ConstraintDescriptor digitsConstraint = (ConstraintDescriptor) descriptor; + int integerDigits = digitsConstraint.getAnnotation().integer(); + int fractionalDigits = digitsConstraint.getAnnotation().fraction(); + Column col = (Column) property.getColumnIterator().next(); + col.setPrecision( integerDigits + fractionalDigits ); + col.setScale( fractionalDigits ); + } + } + + private static void applySize(Property property, ConstraintDescriptor descriptor, PropertyDescriptor propertyDescriptor) { + if ( Size.class.equals( descriptor.getAnnotation().annotationType() ) + && String.class.equals( propertyDescriptor.getElementClass() ) ) { + @SuppressWarnings("unchecked") + ConstraintDescriptor sizeConstraint = (ConstraintDescriptor) descriptor; + int max = sizeConstraint.getAnnotation().max(); + Column col = (Column) property.getColumnIterator().next(); + if ( max < Integer.MAX_VALUE ) { + col.setLength( max ); } } } - private static void applyConstraints(PropertyDescriptor propertyDescriptor, - Set> groups, - AttributeBinding attributeBinding, - Dialect dialect) { - - for ( ConstraintDescriptor constraintDescriptor : propertyDescriptor.getConstraintDescriptors() ) { - if ( groups != null && Collections.disjoint( constraintDescriptor.getGroups(), groups ) ) { - continue; + private static void applyLength(Property property, ConstraintDescriptor descriptor, PropertyDescriptor propertyDescriptor) { + if ( "org.hibernate.validator.constraints.Length".equals( + descriptor.getAnnotation().annotationType().getName() + ) + && String.class.equals( propertyDescriptor.getElementClass() ) ) { + @SuppressWarnings("unchecked") + int max = (Integer) descriptor.getAttributes().get( "max" ); + Column col = (Column) property.getColumnIterator().next(); + if ( max < Integer.MAX_VALUE ) { + col.setLength( max ); } - - for ( SchemaConstraint schemaConstraint : schemaConstraints ) { - schemaConstraint.applyConstraint( attributeBinding, constraintDescriptor, propertyDescriptor, dialect ); - } - - notNullSchemaConstraint.applyConstraint( - attributeBinding, - constraintDescriptor, - propertyDescriptor, - dialect - ); } } /** - * Locates a mapping property of a persistent class by property name - * - * @param associatedClass the persistent class - * @param propertyName the property name - * + * @param associatedClass + * @param propertyName * @return the property by path in a recursive way, including IdentifierProperty in the loop if propertyName is - * null. If propertyName is null or empty, the IdentifierProperty is returned + * null. If propertyName is null or empty, the IdentifierProperty is returned */ private static Property findPropertyByName(PersistentClass associatedClass, String propertyName) { Property property = null; @@ -331,7 +384,7 @@ class TypeSafeActivator { } StringTokenizer st = new StringTokenizer( propertyName, ".", false ); while ( st.hasMoreElements() ) { - String element = ( String ) st.nextElement(); + String element = (String) st.nextElement(); if ( property == null ) { property = associatedClass.getProperty( element ); } @@ -339,7 +392,7 @@ class TypeSafeActivator { if ( !property.isComposite() ) { return null; } - property = ( ( Component ) property.getValue() ).getProperty( element ); + property = ( (Component) property.getValue() ).getProperty( element ); } } } @@ -352,7 +405,7 @@ class TypeSafeActivator { } StringTokenizer st = new StringTokenizer( propertyName, ".", false ); while ( st.hasMoreElements() ) { - String element = ( String ) st.nextElement(); + String element = (String) st.nextElement(); if ( property == null ) { property = associatedClass.getIdentifierMapper().getProperty( element ); } @@ -360,7 +413,7 @@ class TypeSafeActivator { if ( !property.isComposite() ) { return null; } - property = ( ( Component ) property.getValue() ).getProperty( element ); + property = ( (Component) property.getValue() ).getProperty( element ); } } } @@ -371,56 +424,6 @@ class TypeSafeActivator { return property; } - // TODO - remove!? - - /** - * @param entityBinding entity binding for the currently processed entity - * @param attrName - * - * @return the attribute by path in a recursive way, including EntityIdentifier in the loop if attrName is - * {@code null}. If attrName is {@code null} or empty, the EntityIdentifier is returned - */ - private static AttributeBinding findAttributeBindingByName(EntityBinding entityBinding, - String attrName) { - AttributeBinding attrBinding = null; - EntityIdentifier identifier = entityBinding.getHierarchyDetails().getEntityIdentifier(); - BasicAttributeBinding idAttrBinding = null; //identifier.getValueBinding(); - String idAttrName = idAttrBinding != null ? idAttrBinding.getAttribute().getName() : null; - try { - if ( attrName == null || attrName.length() == 0 || attrName.equals( idAttrName ) ) { - attrBinding = idAttrBinding; // default to id - } - else { - if ( attrName.indexOf( idAttrName + "." ) == 0 ) { - attrBinding = idAttrBinding; - attrName = attrName.substring( idAttrName.length() + 1 ); - } - for ( StringTokenizer st = new StringTokenizer( attrName, "." ); st.hasMoreElements(); ) { - String element = st.nextToken(); - if ( attrBinding == null ) { - attrBinding = entityBinding.locateAttributeBinding( element ); - } - else { - return null; // TODO: if (attrBinding.isComposite()) ... - } - } - } - } - catch ( MappingException error ) { - try { - //if we do not find it try to check the identifier mapper - if ( !identifier.isIdentifierMapper() ) { - return null; - } - // TODO: finish once composite/embedded/component IDs get worked out - } - catch ( MappingException ee ) { - return null; - } - } - return attrBinding; - } - private static ValidatorFactory getValidatorFactory(Map properties) { ValidatorFactory factory = null; if ( properties != null ) { @@ -430,7 +433,7 @@ class TypeSafeActivator { factory = ValidatorFactory.class.cast( unsafeProperty ); } catch ( ClassCastException e ) { - throw new HibernateException( + throw new IntegrationException( "Property " + FACTORY_PROPERTY + " should contain an object of type " + ValidatorFactory.class.getName() ); @@ -442,7 +445,7 @@ class TypeSafeActivator { factory = Validation.buildDefaultValidatorFactory(); } catch ( Exception e ) { - throw new HibernateException( "Unable to build the default ValidatorFactory", e ); + throw new IntegrationException( "Unable to build the default ValidatorFactory", e ); } } return factory; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ValidationMode.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ValidationMode.java index 3d989ec8aa..29bbda0413 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ValidationMode.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ValidationMode.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -29,37 +29,41 @@ import java.util.Set; import org.hibernate.HibernateException; /** - * @author Hardy Ferentschik + * Duplicates the javax.validation enum (because javax validation might not be on the runtime classpath) + * + * @author Steve Ebersole */ public enum ValidationMode { - AUTO, - CALLBACK, - NONE, - DDL; + AUTO( "auto" ), + CALLBACK( "callback" ), + NONE( "none" ), + DDL( "ddl" ); + + private final String externalForm; + + private ValidationMode(String externalForm) { + this.externalForm = externalForm; + } public static Set getModes(Object modeProperty) { - Set modes = new HashSet( 3 ); - if ( modeProperty == null ) { + Set modes = new HashSet(3); + if (modeProperty == null) { modes.add( ValidationMode.AUTO ); } else { final String[] modesInString = modeProperty.toString().split( "," ); for ( String modeInString : modesInString ) { - modes.add( getMode( modeInString ) ); + modes.add( getMode(modeInString) ); } } if ( modes.size() > 1 && ( modes.contains( ValidationMode.AUTO ) || modes.contains( ValidationMode.NONE ) ) ) { - StringBuilder message = new StringBuilder( "Incompatible validation modes mixed: " ); - for ( ValidationMode mode : modes ) { - message.append( mode ).append( ", " ); - } - throw new HibernateException( message.substring( 0, message.length() - 2 ) ); + throw new HibernateException( "Incompatible validation modes mixed: " + loggable( modes ) ); } return modes; } private static ValidationMode getMode(String modeProperty) { - if ( modeProperty == null || modeProperty.length() == 0 ) { + if (modeProperty == null || modeProperty.length() == 0) { return AUTO; } else { @@ -71,4 +75,17 @@ public enum ValidationMode { } } } + + public static String loggable(Set modes) { + if ( modes == null || modes.isEmpty() ) { + return "[]"; + } + StringBuilder buffer = new StringBuilder( "[" ); + String sep = ""; + for ( ValidationMode mode : modes ) { + buffer.append( sep ).append( mode.externalForm ); + sep = ", "; + } + return buffer.append( "]" ).toString(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java index 36ceae439b..9ddd1d21aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java @@ -134,7 +134,7 @@ public class PersistentArrayHolder extends AbstractPersistentCollection { throws HibernateException, SQLException { Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ); - int index = ( (Integer) persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() ) ).intValue(); + int index = (Integer) persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() ); for ( int i = tempList.size(); i<=index; i++) { tempList.add(i, null); } @@ -205,7 +205,7 @@ public class PersistentArrayHolder extends AbstractPersistentCollection { } public Iterator getDeletes(CollectionPersister persister, boolean indexIsFormula) throws HibernateException { - java.util.List deletes = new ArrayList(); + java.util.List deletes = new ArrayList(); Serializable sn = getSnapshot(); int snSize = Array.getLength(sn); int arraySize = Array.getLength(array); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java index 526c1c373a..e9b211cb2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java @@ -52,8 +52,6 @@ public class PersistentList extends AbstractPersistentCollection implements List @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { - final EntityMode entityMode = persister.getOwnerEntityPersister().getEntityMode(); - ArrayList clonedList = new ArrayList( list.size() ); for ( Object element : list ) { Object deepCopy = persister.getElementType().deepCopy( element, persister.getFactory() ); @@ -399,7 +397,7 @@ public class PersistentList extends AbstractPersistentCollection implements List public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAliases descriptor, Object owner) throws HibernateException, SQLException { Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ) ; - int index = ( (Integer) persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() ) ).intValue(); + int index = (Integer) persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() ); //pad with nulls from the current last element up to the new index for ( int i = list.size(); i<=index; i++) { diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java index f33a7e2502..997732f133 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java @@ -23,8 +23,9 @@ */ package org.hibernate.context.internal; -import java.util.Hashtable; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + import javax.transaction.Synchronization; import javax.transaction.Transaction; import javax.transaction.TransactionManager; @@ -67,7 +68,7 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; public class JTASessionContext extends AbstractCurrentSessionContext { private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, JTASessionContext.class.getName()); - private transient Map currentSessionMap = new Hashtable(); + private transient Map currentSessionMap = new ConcurrentHashMap(); public JTASessionContext(SessionFactoryImplementor factory) { super( factory ); @@ -103,7 +104,7 @@ public class JTASessionContext extends AbstractCurrentSessionContext { final Object txnIdentifier = jtaPlatform.getTransactionIdentifier( txn ); - Session currentSession = ( Session ) currentSessionMap.get( txnIdentifier ); + Session currentSession = currentSessionMap.get( txnIdentifier ); if ( currentSession == null ) { currentSession = buildOrObtainSession(); diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/NotExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/NotExpression.java index 845b739dac..35bf4e5346 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/NotExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/NotExpression.java @@ -25,12 +25,12 @@ package org.hibernate.criterion; import org.hibernate.Criteria; import org.hibernate.HibernateException; -import org.hibernate.dialect.MySQLDialect; import org.hibernate.engine.spi.TypedValue; /** * Negates another criterion * @author Gavin King + * @author Brett Meyer */ public class NotExpression implements Criterion { @@ -40,14 +40,9 @@ public class NotExpression implements Criterion { this.criterion = criterion; } - public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) - throws HibernateException { - if ( criteriaQuery.getFactory().getDialect() instanceof MySQLDialect ) { - return "not (" + criterion.toSqlString(criteria, criteriaQuery) + ')'; - } - else { - return "not " + criterion.toSqlString(criteria, criteriaQuery); - } + public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { + return criteriaQuery.getFactory().getDialect().getNotExpression( + criterion.toSqlString( criteria, criteriaQuery ) ); } public TypedValue[] getTypedValues( diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Order.java b/hibernate-core/src/main/java/org/hibernate/criterion/Order.java index a5b623d7c0..9888d76400 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Order.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Order.java @@ -28,21 +28,25 @@ import java.sql.Types; import org.hibernate.Criteria; import org.hibernate.HibernateException; +import org.hibernate.NullPrecedence; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.Type; /** * Represents an order imposed upon a Criteria result set + * * @author Gavin King + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + * @author Brett Meyer */ public class Order implements Serializable { - private boolean ascending; private boolean ignoreCase; private String propertyName; + private NullPrecedence nullPrecedence; public String toString() { - return propertyName + ' ' + (ascending?"asc":"desc"); + return propertyName + ' ' + ( ascending ? "asc" : "desc" ) + ( nullPrecedence != null ? ' ' + nullPrecedence.name().toLowerCase() : "" ); } public Order ignoreCase() { @@ -50,6 +54,11 @@ public class Order implements Serializable { return this; } + public Order nulls(NullPrecedence nullPrecedence) { + this.nullPrecedence = nullPrecedence; + return this; + } + /** * Constructor for Order. */ @@ -68,19 +77,46 @@ public class Order implements Serializable { Type type = criteriaQuery.getTypeUsingProjection(criteria, propertyName); StringBuilder fragment = new StringBuilder(); for ( int i=0; i"); } + /** + * Apply a "not equal" constraint to the named property. If the value + * is null, instead apply "is not null". + * @param propertyName + * @param value + * @return Criterion + */ + public static Criterion neOrIsNotNull(String propertyName, Object value) { + if (value == null) { + return isNotNull(propertyName); + } + return new SimpleExpression(propertyName, value, "<>"); + } /** * Apply a "like" constraint to the named property * @param propertyName diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 61f1576f10..2e3397c464 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -462,5 +462,10 @@ public class DB2Dialect extends Dialect { public UniqueDelegate getUniqueDelegate() { return uniqueDelegate; } + + @Override + public String getNotExpression( String expression ) { + return "not (" + expression + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 48176580da..2df7bdd191 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -23,39 +23,14 @@ */ package org.hibernate.dialect; -import java.io.InputStream; -import java.io.OutputStream; -import java.sql.Blob; -import java.sql.CallableStatement; -import java.sql.Clob; -import java.sql.NClob; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; +import org.hibernate.NullPrecedence; import org.hibernate.cfg.Environment; -import org.hibernate.dialect.function.CastFunction; -import org.hibernate.dialect.function.SQLFunction; -import org.hibernate.dialect.function.SQLFunctionTemplate; -import org.hibernate.dialect.function.StandardAnsiSqlAggregationFunctions; -import org.hibernate.dialect.function.StandardSQLFunction; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy; -import org.hibernate.dialect.lock.OptimisticLockingStrategy; -import org.hibernate.dialect.lock.PessimisticForceIncrementLockingStrategy; -import org.hibernate.dialect.lock.PessimisticReadSelectLockingStrategy; -import org.hibernate.dialect.lock.PessimisticWriteSelectLockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.function.*; +import org.hibernate.dialect.lock.*; import org.hibernate.dialect.pagination.LegacyLimitHandler; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.unique.DefaultUniqueDelegate; @@ -96,11 +71,17 @@ import org.hibernate.tool.schema.internal.StandardSequenceExporter; import org.hibernate.tool.schema.internal.StandardTableExporter; import org.hibernate.tool.schema.internal.TemporaryTableExporter; import org.hibernate.tool.schema.spi.Exporter; +import org.hibernate.sql.*; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.jboss.logging.Logger; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.*; +import java.util.*; + /** * Represents a dialect of SQL implemented by a particular RDBMS. * Subclasses implement Hibernate compatibility with different systems.
@@ -1212,6 +1193,8 @@ public abstract class Dialect implements ConversionContext { case FORCE: case PESSIMISTIC_FORCE_INCREMENT: return getForUpdateNowaitString(); + case UPGRADE_SKIPLOCKED: + return getForUpdateSkipLockedString(); default: return ""; } @@ -1330,6 +1313,16 @@ public abstract class Dialect implements ConversionContext { return getForUpdateString(); } + /** + * Retrieves the FOR UPDATE SKIP LOCKED syntax specific to this dialect. + * + * @return The appropriate FOR UPDATE SKIP LOCKED clause string. + */ + public String getForUpdateSkipLockedString() { + // by default we report no support for SKIP_LOCKED lock semantics + return getForUpdateString(); + } + /** * Get the FOR UPDATE OF column_list NOWAIT fragment appropriate * for this dialect given the aliases of the columns to be write locked. @@ -1341,6 +1334,17 @@ public abstract class Dialect implements ConversionContext { return getForUpdateString( aliases ); } + /** + * Get the FOR UPDATE OF column_list SKIP LOCKED fragment appropriate + * for this dialect given the aliases of the columns to be write locked. + * + * @param aliases The columns to be write locked. + * @return The appropriate FOR UPDATE colunm_list SKIP LOCKED clause string. + */ + public String getForUpdateSkipLockedString(String aliases) { + return getForUpdateString( aliases ); + } + /** * Some dialects support an alternative means to SELECT FOR UPDATE, * whereby a "lock hint" is appends to the table name in the from clause. @@ -2059,6 +2063,18 @@ public abstract class Dialect implements ConversionContext { public boolean supportsIfExistsAfterTableName() { return false; } + + public String getDropTableString( String tableName ) { + StringBuilder buf = new StringBuilder( "drop table " ); + if ( supportsIfExistsBeforeTableName() ) { + buf.append( "if exists " ); + } + buf.append( tableName ).append( getCascadeConstraintsString() ); + if ( supportsIfExistsAfterTableName() ) { + buf.append( " if exists" ); + } + return buf.toString(); + } /** * Does this dialect support column-level check constraints? @@ -2202,6 +2218,30 @@ public abstract class Dialect implements ConversionContext { return false; } + /** + * @param expression The SQL order expression. In case of {@code @OrderBy} annotation user receives property placeholder + * (e.g. attribute name enclosed in '{' and '}' signs). + * @param collation Collation string in format {@code collate IDENTIFIER}, or {@code null} + * if expression has not been explicitly specified. + * @param order Order direction. Possible values: {@code asc}, {@code desc}, or {@code null} + * if expression has not been explicitly specified. + * @param nulls Nulls precedence. Default value: {@link NullPrecedence#NONE}. + * @return Renders single element of {@code ORDER BY} clause. + */ + public String renderOrderByElement(String expression, String collation, String order, NullPrecedence nulls) { + final StringBuilder orderByElement = new StringBuilder( expression ); + if ( collation != null ) { + orderByElement.append( " " ).append( collation ); + } + if ( order != null ) { + orderByElement.append( " " ).append( order ); + } + if ( nulls != NullPrecedence.NONE ) { + orderByElement.append( " nulls " ).append( nulls.name().toLowerCase() ); + } + return orderByElement.toString(); + } + /** * Does this dialect require that parameters appearing in the SELECT clause be wrapped in cast() * calls to tell the db parser the expected type. @@ -2439,4 +2479,53 @@ public abstract class Dialect implements ConversionContext { public UniqueDelegate getUniqueDelegate() { return uniqueDelegate; } + + public String getNotExpression( String expression ) { + return "not " + expression; + } + + /** + * Does this dialect support the UNIQUE column syntax? + * + * @return boolean + * + * @deprecated {@link #getUniqueDelegate()} should be overridden instead. + */ + @Deprecated + public boolean supportsUnique() { + return true; + } + + /** + * Does this dialect support adding Unique constraints via create and alter table ? + * + * @return boolean + * + * @deprecated {@link #getUniqueDelegate()} should be overridden instead. + */ + @Deprecated + public boolean supportsUniqueConstraintInCreateAlterTable() { + return true; + } + + /** + * The syntax used to add a unique constraint to a table. + * + * @param constraintName The name of the unique constraint. + * @return The "add unique" fragment + * + * @deprecated {@link #getUniqueDelegate()} should be overridden instead. + */ + @Deprecated + public String getAddUniqueConstraintString(String constraintName) { + return " add constraint " + constraintName + " unique "; + } + + /** + * @deprecated {@link #getUniqueDelegate()} should be overridden instead. + */ + @Deprecated + public boolean supportsNotNullUnique() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InterbaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/InterbaseDialect.java index b04aa0996e..7e0a0b89d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/InterbaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InterbaseDialect.java @@ -25,6 +25,7 @@ package org.hibernate.dialect; import java.sql.Types; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.VarArgsSQLFunction; import org.hibernate.type.StandardBasicTypes; @@ -55,6 +56,7 @@ public class InterbaseDialect extends Dialect { registerColumnType( Types.CLOB, "blob sub_type 1" ); registerFunction( "concat", new VarArgsSQLFunction( StandardBasicTypes.STRING, "(","||",")" ) ); + registerFunction("current_date", new NoArgSQLFunction("current_date", StandardBasicTypes.DATE, false) ); getDefaultProperties().setProperty(Environment.STATEMENT_BATCH_SIZE, NO_BATCH); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index ac33edf5b0..5d9d881577 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -29,6 +29,7 @@ import java.sql.SQLException; import java.sql.Types; import org.hibernate.JDBCException; +import org.hibernate.NullPrecedence; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.StandardSQLFunction; @@ -333,6 +334,24 @@ public class MySQLDialect extends Dialect { return true; } + @Override + public String renderOrderByElement(String expression, String collation, String order, NullPrecedence nulls) { + final StringBuilder orderByElement = new StringBuilder(); + if ( nulls != NullPrecedence.NONE ) { + // Workaround for NULLS FIRST / LAST support. + orderByElement.append( "case when " ).append( expression ).append( " is null then " ); + if ( nulls == NullPrecedence.FIRST ) { + orderByElement.append( "0 else 1" ); + } + else { + orderByElement.append( "1 else 0" ); + } + orderByElement.append( " end, " ); + } + // Nulls precedence has already been handled so passing NONE value. + orderByElement.append( super.renderOrderByElement( expression, collation, order, NullPrecedence.NONE ) ); + return orderByElement.toString(); + } // locking support @@ -396,4 +415,9 @@ public class MySQLDialect extends Dialect { } }; } + + @Override + public String getNotExpression( String expression ) { + return "not (" + expression + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java index 9086ec00d5..8ec1d401a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.dialect; +import org.hibernate.LockOptions; import org.hibernate.sql.ANSIJoinFragment; import org.hibernate.sql.JoinFragment; @@ -45,4 +46,21 @@ public class Oracle10gDialect extends Oracle9iDialect { public JoinFragment createOuterJoinFragment() { return new ANSIJoinFragment(); } + + public String getWriteLockString(int timeout) { + if ( timeout == LockOptions.SKIP_LOCKED ) { + return getForUpdateSkipLockedString(); + } + else { + return super.getWriteLockString(timeout); + } + } + + public String getForUpdateSkipLockedString() { + return " for update skip locked"; + } + + public String getForUpdateSkipLockedString(String aliases) { + return getForUpdateString() + " of " + aliases + " skip locked"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java index faa634c637..123e38e249 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java @@ -581,4 +581,9 @@ public class Oracle8iDialect extends Dialect { public boolean useFollowOnLocking() { return true; } + + @Override + public String getNotExpression( String expression ) { + return "not (" + expression + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java index b3d36326d3..446ffab574 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java @@ -104,6 +104,7 @@ public class PostgreSQL81Dialect extends Dialect { registerFunction( "variance", new StandardSQLFunction("variance", StandardBasicTypes.DOUBLE) ); registerFunction( "random", new NoArgSQLFunction("random", StandardBasicTypes.DOUBLE) ); + registerFunction( "rand", new NoArgSQLFunction("random", StandardBasicTypes.DOUBLE) ); registerFunction( "round", new StandardSQLFunction("round") ); registerFunction( "trunc", new StandardSQLFunction("trunc") ); @@ -118,7 +119,7 @@ public class PostgreSQL81Dialect extends Dialect { registerFunction( "to_ascii", new StandardSQLFunction("to_ascii") ); registerFunction( "quote_ident", new StandardSQLFunction("quote_ident", StandardBasicTypes.STRING) ); registerFunction( "quote_literal", new StandardSQLFunction("quote_literal", StandardBasicTypes.STRING) ); - registerFunction( "md5", new StandardSQLFunction("md5") ); + registerFunction( "md5", new StandardSQLFunction("md5", StandardBasicTypes.STRING) ); registerFunction( "ascii", new StandardSQLFunction("ascii", StandardBasicTypes.INTEGER) ); registerFunction( "char_length", new StandardSQLFunction("char_length", StandardBasicTypes.LONG) ); registerFunction( "bit_length", new StandardSQLFunction("bit_length", StandardBasicTypes.LONG) ); @@ -354,7 +355,7 @@ public class PostgreSQL81Dialect extends Dialect { private static ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { public String extractConstraintName(SQLException sqle) { try { - int sqlState = Integer.valueOf( JdbcExceptionHelper.extractSqlState( sqle )).intValue(); + int sqlState = Integer.valueOf( JdbcExceptionHelper.extractSqlState( sqle ) ); switch (sqlState) { // CHECK VIOLATION case 23514: return extractUsingTemplate("violates check constraint \"","\"", sqle.getMessage()); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java index 4ee4697b8a..342fbf8a77 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java @@ -64,7 +64,6 @@ public class SQLServer2005Dialect extends SQLServerDialect { registerColumnType( Types.BIGINT, "bigint" ); registerColumnType( Types.BIT, "bit" ); - registerColumnType( Types.BOOLEAN, "bit" ); registerFunction( "row_number", new NoArgSQLFunction( "row_number", StandardBasicTypes.INTEGER, true ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 90a9e4b829..0ae137cb5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -47,6 +47,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { registerColumnType( Types.VARBINARY, 8000, "varbinary($l)" ); registerColumnType( Types.LONGVARBINARY, "image" ); registerColumnType( Types.LONGVARCHAR, "text" ); + registerColumnType( Types.BOOLEAN, "bit" ); registerFunction( "second", new SQLFunctionTemplate( StandardBasicTypes.INTEGER, "datepart(second, ?1)" ) ); registerFunction( "minute", new SQLFunctionTemplate( StandardBasicTypes.INTEGER, "datepart(minute, ?1)" ) ); @@ -133,6 +134,8 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { return tableName + " with (updlock, rowlock)"; case PESSIMISTIC_READ: return tableName + " with (holdlock, rowlock)"; + case UPGRADE_SKIPLOCKED: + return tableName + " with (updlock, rowlock, readpast)"; default: return tableName; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index 4064b2347a..90f1c35d20 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -26,6 +26,7 @@ package org.hibernate.dialect; import java.sql.Types; import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -48,6 +49,19 @@ public class SybaseDialect extends AbstractTransactSQLDialect { @Override protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { - return sqlCode == Types.BLOB ? BlobTypeDescriptor.PRIMITIVE_ARRAY_BINDING : super.getSqlTypeDescriptorOverride( sqlCode ); + switch (sqlCode) { + case Types.BLOB: + return BlobTypeDescriptor.PRIMITIVE_ARRAY_BINDING; + case Types.CLOB: + // Some Sybase drivers cannot support getClob. See HHH-7889 + return ClobTypeDescriptor.STREAM_BINDING_EXTRACTING; + default: + return super.getSqlTypeDescriptorOverride( sqlCode ); + } + } + + @Override + public String getNullColumnString() { + return " null"; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java index 9eec303f82..0414770be6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.dialect.function; + import java.util.HashMap; import java.util.Map; @@ -30,19 +31,18 @@ import org.hibernate.dialect.Dialect; public class SQLFunctionRegistry { private final Dialect dialect; private final Map userFunctions; - + public SQLFunctionRegistry(Dialect dialect, Map userFunctions) { this.dialect = dialect; - this.userFunctions = new HashMap(); - this.userFunctions.putAll( userFunctions ); + this.userFunctions = new HashMap( userFunctions ); } - + public SQLFunction findSQLFunction(String functionName) { String name = functionName.toLowerCase(); SQLFunction userFunction = userFunctions.get( name ); return userFunction != null ? userFunction - : (SQLFunction) dialect.getFunctions().get( name ); + : dialect.getFunctions().get( name ); } public boolean hasFunction(String functionName) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java index 68247fa9e3..d88f70cfe0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java @@ -84,7 +84,7 @@ public class TemplateRenderer { chunks = chunkList.toArray( new String[chunkList.size()] ); paramIndexes = new int[paramList.size()]; for ( int i = 0; i < paramIndexes.length; ++i ) { - paramIndexes[i] = paramList.get( i ).intValue(); + paramIndexes[i] = paramList.get( i ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java index 6beb3c40f6..daea92d3d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java @@ -54,11 +54,18 @@ public abstract class AbstractSelectLockingStrategy implements LockingStrategy { protected abstract String generateLockString(int lockTimeout); protected String determineSql(int timeout) { - return timeout == LockOptions.WAIT_FOREVER - ? waitForeverSql - : timeout == LockOptions.NO_WAIT - ? getNoWaitSql() - : generateLockString( timeout ); + if ( timeout == LockOptions.WAIT_FOREVER) { + return waitForeverSql; + } + else if ( timeout == LockOptions.NO_WAIT) { + return getNoWaitSql(); + } + else if ( timeout == LockOptions.SKIP_LOCKED) { + return getSkipLockedSql(); + } + else { + return generateLockString( timeout ); + } } private String noWaitSql; @@ -69,4 +76,13 @@ public abstract class AbstractSelectLockingStrategy implements LockingStrategy { } return noWaitSql; } + + private String skipLockedSql; + + public String getSkipLockedSql() { + if ( skipLockedSql == null ) { + skipLockedSql = generateLockString( LockOptions.SKIP_LOCKED ); + } + return skipLockedSql; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java index e2ee073d02..84580470aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java @@ -85,7 +85,7 @@ public class PessimisticReadSelectLockingStrategy extends AbstractSelectLockingS ); } - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { if ( !rs.next() ) { if ( factory.getStatistics().isStatisticsEnabled() ) { @@ -96,11 +96,11 @@ public class PessimisticReadSelectLockingStrategy extends AbstractSelectLockingS } } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java index 7f65fcfcec..435defc92d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java @@ -104,7 +104,7 @@ public class PessimisticReadUpdateLockingStrategy implements LockingStrategy { lockable.getVersionType().nullSafeSet( st, version, offset, session ); } - int affected = st.executeUpdate(); + int affected = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ); if ( affected < 0 ) { // todo: should this instead check for exactly one row modified? if (factory.getStatistics().isStatisticsEnabled()) { factory.getStatisticsImplementor().optimisticFailure( lockable.getEntityName() ); @@ -114,7 +114,7 @@ public class PessimisticReadUpdateLockingStrategy implements LockingStrategy { } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java index a3341bf159..660dbdaf6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java @@ -84,7 +84,7 @@ public class PessimisticWriteSelectLockingStrategy extends AbstractSelectLocking ); } - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { if ( !rs.next() ) { if ( factory.getStatistics().isStatisticsEnabled() ) { @@ -95,11 +95,11 @@ public class PessimisticWriteSelectLockingStrategy extends AbstractSelectLocking } } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch ( SQLException e ) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java index 83622057c0..3f47ffa176 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java @@ -103,7 +103,7 @@ public class PessimisticWriteUpdateLockingStrategy implements LockingStrategy { lockable.getVersionType().nullSafeSet( st, version, offset, session ); } - int affected = st.executeUpdate(); + int affected = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ); if ( affected < 0 ) { // todo: should this instead check for exactly one row modified? if (factory.getStatistics().isStatisticsEnabled()) { factory.getStatisticsImplementor().optimisticFailure( lockable.getEntityName() ); @@ -113,7 +113,7 @@ public class PessimisticWriteUpdateLockingStrategy implements LockingStrategy { } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch ( SQLException e ) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java index 8bf732f049..7e7926da46 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java @@ -85,7 +85,7 @@ public class SelectLockingStrategy extends AbstractSelectLockingStrategy { ); } - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { if ( !rs.next() ) { if ( factory.getStatistics().isStatisticsEnabled() ) { @@ -96,11 +96,11 @@ public class SelectLockingStrategy extends AbstractSelectLockingStrategy { } } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java index 10b7fd2986..5e8fdfe263 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java @@ -106,7 +106,7 @@ public class UpdateLockingStrategy implements LockingStrategy { lockable.getVersionType().nullSafeSet( st, version, offset, session ); } - int affected = st.executeUpdate(); + int affected = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ); if ( affected < 0 ) { if (factory.getStatistics().isStatisticsEnabled()) { factory.getStatisticsImplementor().optimisticFailure( lockable.getEntityName() ); @@ -116,7 +116,7 @@ public class UpdateLockingStrategy implements LockingStrategy { } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java index 8930a4d583..b16d22639e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java @@ -121,10 +121,12 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { /** * Adds missing aliases in provided SELECT clause and returns coma-separated list of them. + * If query takes advantage of expressions like {@literal *} or {@literal {table}.*} inside SELECT clause, + * method returns {@literal *}. * * @param sb SQL query. * - * @return List of aliases separated with comas. + * @return List of aliases separated with comas or {@literal *}. */ protected String fillAliasInSelectClause(StringBuilder sb) { final List aliases = new LinkedList(); @@ -133,6 +135,7 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { int nextComa = startPos; int prevComa = startPos; int unique = 0; + boolean selectsMultipleColumns = false; while ( nextComa != -1 ) { prevComa = nextComa; @@ -142,30 +145,51 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { } if ( nextComa != -1 ) { String expression = sb.substring( prevComa, nextComa ); - String alias = getAlias( expression ); - if ( alias == null ) { - // Inserting alias. It is unlikely that we would have to add alias, but just in case. - alias = StringHelper.generateAlias( "page", unique ); - sb.insert( nextComa, " as " + alias ); - ++unique; - nextComa += ( " as " + alias ).length(); + if ( selectsMultipleColumns( expression ) ) { + selectsMultipleColumns = true; + } + else { + String alias = getAlias( expression ); + if ( alias == null ) { + // Inserting alias. It is unlikely that we would have to add alias, but just in case. + alias = StringHelper.generateAlias( "page", unique ); + sb.insert( nextComa, " as " + alias ); + ++unique; + nextComa += ( " as " + alias ).length(); + } + aliases.add( alias ); } - aliases.add( alias ); ++nextComa; } } // Processing last column. endPos = shallowIndexOfWord( sb, FROM, startPos ); // Refreshing end position, because we might have inserted new alias. String expression = sb.substring( prevComa, endPos ); - String alias = getAlias( expression ); - if ( alias == null ) { - // Inserting alias. It is unlikely that we would have to add alias, but just in case. - alias = StringHelper.generateAlias( "page", unique ); - sb.insert( endPos - 1, " as " + alias ); + if ( selectsMultipleColumns( expression ) ) { + selectsMultipleColumns = true; + } + else { + String alias = getAlias( expression ); + if ( alias == null ) { + // Inserting alias. It is unlikely that we would have to add alias, but just in case. + alias = StringHelper.generateAlias( "page", unique ); + sb.insert( endPos - 1, " as " + alias ); + } + aliases.add( alias ); } - aliases.add( alias ); - return StringHelper.join( ", ", aliases.iterator() ); + // In case of '*' or '{table}.*' expressions adding an alias breaks SQL syntax, returning '*'. + return selectsMultipleColumns ? "*" : StringHelper.join( ", ", aliases.iterator() ); + } + + /** + * @param expression Select expression. + * + * @return {@code true} when expression selects multiple columns, {@code false} otherwise. + */ + private boolean selectsMultipleColumns(String expression) { + final String lastExpr = expression.trim().replaceFirst( "(?i)(.)*\\s", "" ); + return "*".equals( lastExpr ) || lastExpr.endsWith( ".*" ); } /** @@ -179,7 +203,9 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { private String getAlias(String expression) { Matcher matcher = ALIAS_PATTERN.matcher( expression ); if ( matcher.find() ) { - return matcher.group( 0 ).replaceFirst( "(?i)\\sas\\s", "" ).trim(); + // Taking advantage of Java regular expressions greedy behavior while extracting the last AS keyword. + // Note that AS keyword can appear in CAST operator, e.g. 'cast(tab1.col1 as varchar(255)) as col1'. + return matcher.group( 0 ).replaceFirst( "(?i)(.)*\\sas\\s", "" ).trim(); } return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/DB2UniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DB2UniqueDelegate.java index 312a8354e0..567244f333 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/DB2UniqueDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DB2UniqueDelegate.java @@ -45,7 +45,7 @@ public class DB2UniqueDelegate extends DefaultUniqueDelegate { if ( hasNullable( uniqueKey ) ) { return org.hibernate.mapping.Index.buildSqlCreateIndexString( dialect, uniqueKey.getName(), uniqueKey.getTable(), - uniqueKey.columnIterator(), true, defaultCatalog, + uniqueKey.columnIterator(), uniqueKey.getColumnOrderMap(), true, defaultCatalog, defaultSchema ); } else { return super.applyUniquesOnAlter( @@ -110,9 +110,9 @@ public class DB2UniqueDelegate extends DefaultUniqueDelegate { } private boolean hasNullable( org.hibernate.mapping.UniqueKey uniqueKey ) { - Iterator iter = uniqueKey.getColumnIterator(); + Iterator iter = uniqueKey.columnIterator(); while ( iter.hasNext() ) { - if ( ( ( org.hibernate.mapping.Column ) iter.next() ).isNullable() ) { + if ( iter.next().isNullable() ) { return true; } } @@ -120,9 +120,8 @@ public class DB2UniqueDelegate extends DefaultUniqueDelegate { } private boolean hasNullable( UniqueKey uniqueKey ) { - Iterator iter = uniqueKey.getColumns().iterator(); - while ( iter.hasNext() ) { - if ( ( ( Column ) iter.next() ).isNullable() ) { + for ( Column column : uniqueKey.getColumns() ) { + if ( column.isNullable() ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java index e93882064c..40cb53d684 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/DefaultUniqueDelegate.java @@ -115,11 +115,14 @@ public class DefaultUniqueDelegate implements UniqueDelegate { public String uniqueConstraintSql( org.hibernate.mapping.UniqueKey uniqueKey ) { StringBuilder sb = new StringBuilder(); sb.append( " unique (" ); - Iterator columnIterator = uniqueKey.getColumnIterator(); + Iterator columnIterator = uniqueKey.columnIterator(); while ( columnIterator.hasNext() ) { org.hibernate.mapping.Column column - = (org.hibernate.mapping.Column) columnIterator.next(); + = columnIterator.next(); sb.append( column.getQuotedName( dialect ) ); + if ( uniqueKey.getColumnOrderMap().containsKey( column ) ) { + sb.append( " " ).append( uniqueKey.getColumnOrderMap().get( column ) ); + } if ( columnIterator.hasNext() ) { sb.append( ", " ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index c9463b5659..12400c4876 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -255,13 +255,17 @@ public final class Cascade { final EntityEntry valueEntry = eventSource .getPersistenceContext().getEntry( loadedValue ); - final String entityName = valueEntry.getPersister().getEntityName(); - if ( LOG.isTraceEnabled() ) { - final Serializable id = valueEntry.getPersister().getIdentifier( loadedValue, eventSource ); - final String description = MessageHelper.infoString( entityName, id ); - LOG.tracev( "Deleting orphaned entity instance: {0}", description ); + // Need to check this in case the context has + // already been flushed. See HHH-7829. + if ( valueEntry != null ) { + final String entityName = valueEntry.getPersister().getEntityName(); + if ( LOG.isTraceEnabled() ) { + final Serializable id = valueEntry.getPersister().getIdentifier( loadedValue, eventSource ); + final String description = MessageHelper.infoString( entityName, id ); + LOG.tracev( "Deleting orphaned entity instance: {0}", description ); + } + eventSource.delete( entityName, loadedValue, false, new HashSet() ); } - eventSource.delete( entityName, loadedValue, false, new HashSet() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java new file mode 100644 index 0000000000..0cb3e89e83 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java @@ -0,0 +1,422 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.internal; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.IdentityHashMap; +import java.util.Map; + +import org.jboss.logging.Logger; + +import org.hibernate.AssertionFailure; +import org.hibernate.LockMode; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; + +/** + * Defines a context for maintaining the relation between an entity associated with the Session ultimately owning this + * EntityEntryContext instance and that entity's corresponding EntityEntry. 2 approaches are supported:
    + *
  • + * the entity->EntityEntry association is maintained in a Map within this class + *
  • + *
  • + * the EntityEntry is injected into the entity via it implementing the {@link org.hibernate.engine.spi.ManagedEntity} contract, + * either directly or through bytecode enhancement. + *
  • + *
+ *

+ * + * @author Steve Ebersole + */ +public class EntityEntryContext { + private static final Logger log = Logger.getLogger( EntityEntryContext.class ); + + private transient ManagedEntity head; + private transient ManagedEntity tail; + private transient int count = 0; + + private transient IdentityHashMap nonEnhancedEntityXref; + + @SuppressWarnings( {"unchecked"}) + private transient Map.Entry[] reentrantSafeEntries = new Map.Entry[0]; + private transient boolean dirty = false; + + public EntityEntryContext() { + } + + public void addEntityEntry(Object entity, EntityEntry entityEntry) { + // IMPORTANT!!!!! + // add is called more than once of some entities. In such cases the first + // call is simply setting up a "marker" to avoid infinite looping from reentrancy + // + // any addition (even the double one described above) should invalidate the cross-ref array + dirty = true; + + // determine the appropriate ManagedEntity instance to use based on whether the entity is enhanced or not. + // also track whether the entity was already associated with the context + final ManagedEntity managedEntity; + final boolean alreadyAssociated; + if ( ManagedEntity.class.isInstance( entity ) ) { + managedEntity = (ManagedEntity) entity; + alreadyAssociated = managedEntity.$$_hibernate_getEntityEntry() != null; + } + else { + ManagedEntity wrapper = null; + if ( nonEnhancedEntityXref == null ) { + nonEnhancedEntityXref = new IdentityHashMap(); + } + else { + wrapper = nonEnhancedEntityXref.get( entity ); + } + + if ( wrapper == null ) { + wrapper = new ManagedEntityImpl( entity ); + nonEnhancedEntityXref.put( entity, wrapper ); + alreadyAssociated = false; + } + else { + alreadyAssociated = true; + } + + managedEntity = wrapper; + } + + // associate the EntityEntry with the entity + managedEntity.$$_hibernate_setEntityEntry( entityEntry ); + + if ( alreadyAssociated ) { + // if the entity was already associated with the context, skip the linking step. + return; + } + + // finally, set up linking and count + if ( tail == null ) { + assert head == null; + head = managedEntity; + tail = head; + count = 1; + } + else { + tail.$$_hibernate_setNextManagedEntity( managedEntity ); + managedEntity.$$_hibernate_setPreviousManagedEntity( tail ); + tail = managedEntity; + count++; + } + } + + public boolean hasEntityEntry(Object entity) { + return getEntityEntry( entity ) != null; + } + + public EntityEntry getEntityEntry(Object entity) { + final ManagedEntity managedEntity; + if ( ManagedEntity.class.isInstance( entity ) ) { + managedEntity = (ManagedEntity) entity; + } + else if ( nonEnhancedEntityXref == null ) { + managedEntity = null; + } + else { + managedEntity = nonEnhancedEntityXref.get( entity ); + } + + return managedEntity == null + ? null + : managedEntity.$$_hibernate_getEntityEntry(); + } + + public EntityEntry removeEntityEntry(Object entity) { + dirty = true; + + final ManagedEntity managedEntity; + if ( ManagedEntity.class.isInstance( entity ) ) { + managedEntity = (ManagedEntity) entity; + } + else if ( nonEnhancedEntityXref == null ) { + managedEntity = null; + } + else { + managedEntity = nonEnhancedEntityXref.remove( entity ); + } + + if ( managedEntity == null ) { + return null; + } + + // prepare for re-linking... + ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity(); + ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity(); + managedEntity.$$_hibernate_setPreviousManagedEntity( null ); + managedEntity.$$_hibernate_setNextManagedEntity( null ); + + count--; + + if ( count == 0 ) { + // handle as a special case... + head = null; + tail = null; + + assert previous == null; + assert next == null; + } + else { + // otherwise, previous or next (or both) should be non-null + if ( previous == null ) { + // we are removing head + assert managedEntity == head; + head = next; + } + else { + previous.$$_hibernate_setNextManagedEntity( next ); + } + + if ( next == null ) { + // we are removing tail + assert managedEntity == tail; + tail = previous; + } + else { + next.$$_hibernate_setPreviousManagedEntity( previous ); + } + } + + EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry(); + managedEntity.$$_hibernate_setEntityEntry( null ); + return theEntityEntry; + } + + public Map.Entry[] reentrantSafeEntityEntries() { + if ( dirty ) { + reentrantSafeEntries = new EntityEntryCrossRefImpl[count]; + int i = 0; + ManagedEntity managedEntity = head; + while ( managedEntity != null ) { + reentrantSafeEntries[i++] = new EntityEntryCrossRefImpl( + managedEntity.$$_hibernate_getEntityInstance(), + managedEntity.$$_hibernate_getEntityEntry() + ); + managedEntity = managedEntity.$$_hibernate_getNextManagedEntity(); + } + dirty = false; + } + return reentrantSafeEntries; + } + + public void clear() { + dirty = true; + + ManagedEntity node = head; + while ( node != null ) { + final ManagedEntity nextNode = node.$$_hibernate_getNextManagedEntity(); + + node.$$_hibernate_setEntityEntry( null ); + node.$$_hibernate_setPreviousManagedEntity( null ); + node.$$_hibernate_setNextManagedEntity( null ); + + node = nextNode; + } + + if ( nonEnhancedEntityXref != null ) { + nonEnhancedEntityXref.clear(); + } + + head = null; + tail = null; + count = 0; + + reentrantSafeEntries = null; + } + + public void downgradeLocks() { + if ( head == null ) { + return; + } + + ManagedEntity node = head; + while ( node != null ) { + node.$$_hibernate_getEntityEntry().setLockMode( LockMode.NONE ); + + node = node.$$_hibernate_getNextManagedEntity(); + } + } + + public void serialize(ObjectOutputStream oos) throws IOException { + log.tracef( "Starting serialization of [%s] EntityEntry entries", count ); + oos.writeInt( count ); + if ( count == 0 ) { + return; + } + + ManagedEntity managedEntity = head; + while ( managedEntity != null ) { + // so we know whether or not to build a ManagedEntityImpl on deserialize + oos.writeBoolean( managedEntity == managedEntity.$$_hibernate_getEntityInstance() ); + oos.writeObject( managedEntity.$$_hibernate_getEntityInstance() ); + managedEntity.$$_hibernate_getEntityEntry().serialize( oos ); + + managedEntity = managedEntity.$$_hibernate_getNextManagedEntity(); + } + } + + public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPersistenceContext rtn) throws IOException, ClassNotFoundException { + final int count = ois.readInt(); + log.tracef( "Starting deserialization of [%s] EntityEntry entries", count ); + + final EntityEntryContext context = new EntityEntryContext(); + context.count = count; + context.dirty = true; + + if ( count == 0 ) { + return context; + } + + ManagedEntity previous = null; + + for ( int i = 0; i < count; i++ ) { + final boolean isEnhanced = ois.readBoolean(); + final Object entity = ois.readObject(); + final EntityEntry entry = EntityEntry.deserialize( ois, rtn ); + final ManagedEntity managedEntity; + if ( isEnhanced ) { + managedEntity = (ManagedEntity) entity; + } + else { + managedEntity = new ManagedEntityImpl( entity ); + if ( context.nonEnhancedEntityXref == null ) { + context.nonEnhancedEntityXref = new IdentityHashMap(); + } + context.nonEnhancedEntityXref.put( entity, managedEntity ); + } + managedEntity.$$_hibernate_setEntityEntry( entry ); + + if ( previous == null ) { + context.head = managedEntity; + } + else { + previous.$$_hibernate_setNextManagedEntity( managedEntity ); + managedEntity.$$_hibernate_setPreviousManagedEntity( previous ); + } + + previous = managedEntity; + } + + context.tail = previous; + + return context; + } + + public int getNumberOfManagedEntities() { + return count; + } + + private static class ManagedEntityImpl implements ManagedEntity { + private final Object entityInstance; + private EntityEntry entityEntry; + private ManagedEntity previous; + private ManagedEntity next; + + public ManagedEntityImpl(Object entityInstance) { + this.entityInstance = entityInstance; + } + + @Override + public Object $$_hibernate_getEntityInstance() { + return entityInstance; + } + + @Override + public EntityEntry $$_hibernate_getEntityEntry() { + return entityEntry; + } + + @Override + public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) { + this.entityEntry = entityEntry; + } + + @Override + public ManagedEntity $$_hibernate_getNextManagedEntity() { + return next; + } + + @Override + public void $$_hibernate_setNextManagedEntity(ManagedEntity next) { + this.next = next; + } + + @Override + public ManagedEntity $$_hibernate_getPreviousManagedEntity() { + return previous; + } + + @Override + public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) { + this.previous = previous; + } + } + + private static class EntityEntryCrossRefImpl implements EntityEntryCrossRef { + private final Object entity; + private EntityEntry entityEntry; + + private EntityEntryCrossRefImpl(Object entity, EntityEntry entityEntry) { + this.entity = entity; + this.entityEntry = entityEntry; + } + + @Override + public Object getEntity() { + return entity; + } + + @Override + public EntityEntry getEntityEntry() { + return entityEntry; + } + + @Override + public Object getKey() { + return getEntity(); + } + + @Override + public EntityEntry getValue() { + return getEntityEntry(); + } + + @Override + public EntityEntry setValue(EntityEntry entityEntry) { + final EntityEntry old = this.entityEntry; + this.entityEntry = entityEntry; + return old; + } + } + + public static interface EntityEntryCrossRef extends Map.Entry { + public Object getEntity(); + public EntityEntry getEntityEntry(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java index 6531a15b74..0908e87e5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java @@ -292,7 +292,7 @@ public class JoinSequence { } public Join getFirstJoin() { - return (Join) joins.get( 0 ); + return joins.get( 0 ); } public static interface Selector { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 0af6499f14..15e87dca8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -97,7 +97,7 @@ public class StatefulPersistenceContext implements PersistenceContext { public static final Object NO_ROW = new MarkerObject( "NO_ROW" ); - private static final int INIT_COLL_SIZE = 8; + public static final int INIT_COLL_SIZE = 8; private SessionImplementor session; @@ -107,8 +107,8 @@ public class StatefulPersistenceContext implements PersistenceContext { // Loaded entity instances, by EntityUniqueKey private Map entitiesByUniqueKey; - // Identity map of EntityEntry instances, by the entity instance - private Map entityEntries; + private EntityEntryContext entityEntryContext; +// private Map entityEntries; // Entity proxies, by EntityKey private Map proxiesByKey; @@ -155,7 +155,6 @@ public class StatefulPersistenceContext implements PersistenceContext { private BatchFetchQueue batchFetchQueue; - /** * Constructs a PersistentContext, bound to the given session. * @@ -170,9 +169,10 @@ public class StatefulPersistenceContext implements PersistenceContext { proxiesByKey = new ConcurrentReferenceHashMap( INIT_COLL_SIZE, .75f, 1, ConcurrentReferenceHashMap.ReferenceType.STRONG, ConcurrentReferenceHashMap.ReferenceType.WEAK, null ); entitySnapshotsByKey = new HashMap( INIT_COLL_SIZE ); - entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); + entityEntryContext = new EntityEntryContext(); +// entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); - parentsByChild = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); + parentsByChild = new IdentityHashMap( INIT_COLL_SIZE ); collectionsByKey = new HashMap( INIT_COLL_SIZE ); arrayHolders = new IdentityHashMap( INIT_COLL_SIZE ); @@ -241,7 +241,8 @@ public class StatefulPersistenceContext implements PersistenceContext { arrayHolders.clear(); entitiesByKey.clear(); entitiesByUniqueKey.clear(); - entityEntries.clear(); + entityEntryContext.clear(); +// entityEntries.clear(); parentsByChild.clear(); entitySnapshotsByKey.clear(); collectionsByKey.clear(); @@ -292,10 +293,11 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void afterTransactionCompletion() { cleanUpInsertedKeysAfterTransaction(); - // Downgrade locks - for ( EntityEntry o : entityEntries.values() ) { - o.setLockMode( LockMode.NONE ); - } + entityEntryContext.downgradeLocks(); +// // Downgrade locks +// for ( EntityEntry o : entityEntries.values() ) { +// o.setLockMode( LockMode.NONE ); +// } } /** @@ -454,7 +456,8 @@ public class StatefulPersistenceContext implements PersistenceContext { */ @Override public EntityEntry getEntry(Object entity) { - return entityEntries.get(entity); + return entityEntryContext.getEntityEntry( entity ); +// return entityEntries.get(entity); } /** @@ -462,7 +465,8 @@ public class StatefulPersistenceContext implements PersistenceContext { */ @Override public EntityEntry removeEntry(Object entity) { - return entityEntries.remove(entity); + return entityEntryContext.removeEntityEntry( entity ); +// return entityEntries.remove(entity); } /** @@ -470,7 +474,8 @@ public class StatefulPersistenceContext implements PersistenceContext { */ @Override public boolean isEntryFor(Object entity) { - return entityEntries.containsKey(entity); + return entityEntryContext.hasEntityEntry( entity ); +// return entityEntries.containsKey(entity); } /** @@ -546,7 +551,9 @@ public class StatefulPersistenceContext implements PersistenceContext { lazyPropertiesAreUnfetched, this ); - entityEntries.put(entity, e); + + entityEntryContext.addEntityEntry( entity, e ); +// entityEntries.put(entity, e); setHasNonReadOnlyEnties(status); return e; @@ -1145,9 +1152,14 @@ public class StatefulPersistenceContext implements PersistenceContext { return proxiesByKey; } + @Override + public int getNumberOfManagedEntities() { + return entityEntryContext.getNumberOfManagedEntities(); + } + @Override public Map getEntityEntries() { - return entityEntries; + return null; } @Override @@ -1226,6 +1238,11 @@ public class StatefulPersistenceContext implements PersistenceContext { .toString(); } + @Override + public Entry[] reentrantSafeEntityEntries() { + return entityEntryContext.reentrantSafeEntityEntries(); + } + /** * Search this persistence context for an associated entity instance which is considered the "owner" of * the given childEntity, and return that owner's id value. This is performed in the scenario of a @@ -1256,7 +1273,8 @@ public class StatefulPersistenceContext implements PersistenceContext { // try cache lookup first Object parent = parentsByChild.get( childEntity ); if ( parent != null ) { - final EntityEntry entityEntry = entityEntries.get( parent ); + final EntityEntry entityEntry = entityEntryContext.getEntityEntry( parent ); +// final EntityEntry entityEntry = entityEntries.get( parent ); //there maybe more than one parent, filter by type if ( persister.isSubclassEntityName(entityEntry.getEntityName() ) && isFoundInParent( propertyName, childEntity, persister, collectionPersister, parent ) ) { @@ -1269,7 +1287,8 @@ public class StatefulPersistenceContext implements PersistenceContext { //not found in case, proceed // iterate all the entities currently associated with the persistence context. - for ( Entry me : IdentityMap.concurrentEntries( entityEntries ) ) { + for ( Entry me : reentrantSafeEntityEntries() ) { +// for ( Entry me : IdentityMap.concurrentEntries( entityEntries ) ) { final EntityEntry entityEntry = me.getValue(); // does this entity entry pertain to the entity persister in which we are interested (owner)? if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) { @@ -1369,7 +1388,8 @@ public class StatefulPersistenceContext implements PersistenceContext { // try cache lookup first Object parent = parentsByChild.get(childEntity); if (parent != null) { - final EntityEntry entityEntry = entityEntries.get(parent); + final EntityEntry entityEntry = entityEntryContext.getEntityEntry( parent ); +// final EntityEntry entityEntry = entityEntries.get(parent); //there maybe more than one parent, filter by type if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) { Object index = getIndexInParent(property, childEntity, persister, cp, parent); @@ -1391,7 +1411,7 @@ public class StatefulPersistenceContext implements PersistenceContext { } //Not found in cache, proceed - for ( Entry me : IdentityMap.concurrentEntries( entityEntries ) ) { + for ( Entry me : reentrantSafeEntityEntries() ) { EntityEntry ee = me.getValue(); if ( persister.isSubclassEntityName( ee.getEntityName() ) ) { Object instance = me.getKey(); @@ -1515,8 +1535,8 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId) { - Object entity = entitiesByKey.remove( oldKey ); - EntityEntry oldEntry = entityEntries.remove( entity ); + final Object entity = entitiesByKey.remove( oldKey ); + final EntityEntry oldEntry = entityEntryContext.removeEntityEntry( entity ); parentsByChild.clear(); final EntityKey newKey = session.generateEntityKey( generatedId, oldEntry.getPersister() ); @@ -1586,14 +1606,15 @@ public class StatefulPersistenceContext implements PersistenceContext { oos.writeObject( entry.getValue() ); } - oos.writeInt( entityEntries.size() ); - if ( tracing ) LOG.trace("Starting serialization of [" + entityEntries.size() + "] entityEntries entries"); - itr = entityEntries.entrySet().iterator(); - while ( itr.hasNext() ) { - Map.Entry entry = ( Map.Entry ) itr.next(); - oos.writeObject( entry.getKey() ); - ( ( EntityEntry ) entry.getValue() ).serialize( oos ); - } + entityEntryContext.serialize( oos ); +// oos.writeInt( entityEntries.size() ); +// if ( tracing ) LOG.trace("Starting serialization of [" + entityEntries.size() + "] entityEntries entries"); +// itr = entityEntries.entrySet().iterator(); +// while ( itr.hasNext() ) { +// Map.Entry entry = ( Map.Entry ) itr.next(); +// oos.writeObject( entry.getKey() ); +// ( ( EntityEntry ) entry.getValue() ).serialize( oos ); +// } oos.writeInt( collectionsByKey.size() ); if ( tracing ) LOG.trace("Starting serialization of [" + collectionsByKey.size() + "] collectionsByKey entries"); @@ -1690,14 +1711,15 @@ public class StatefulPersistenceContext implements PersistenceContext { rtn.entitySnapshotsByKey.put( EntityKey.deserialize( ois, session ), ois.readObject() ); } - count = ois.readInt(); - if ( tracing ) LOG.trace("Starting deserialization of [" + count + "] entityEntries entries"); - rtn.entityEntries = IdentityMap.instantiateSequenced( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); - for ( int i = 0; i < count; i++ ) { - Object entity = ois.readObject(); - EntityEntry entry = EntityEntry.deserialize( ois, rtn ); - rtn.entityEntries.put( entity, entry ); - } + rtn.entityEntryContext = EntityEntryContext.deserialize( ois, rtn ); +// count = ois.readInt(); +// if ( tracing ) LOG.trace("Starting deserialization of [" + count + "] entityEntries entries"); +// rtn.entityEntries = IdentityMap.instantiateSequenced( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); +// for ( int i = 0; i < count; i++ ) { +// Object entity = ois.readObject(); +// EntityEntry entry = EntityEntry.deserialize( ois, rtn ); +// rtn.entityEntries.put( entity, entry ); +// } count = ois.readInt(); if ( tracing ) LOG.trace("Starting deserialization of [" + count + "] collectionsByKey entries"); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java index cabcfb7be0..f3e890ff7b 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -127,6 +127,9 @@ public final class TwoPhaseLoad { final PostLoadEvent postLoadEvent) throws HibernateException { final PersistenceContext persistenceContext = session.getPersistenceContext(); final EntityEntry entityEntry = persistenceContext.getEntry(entity); + if ( entityEntry == null ) { + throw new AssertionFailure( "possible non-threadsafe access to the session" ); + } final EntityPersister persister = entityEntry.getPersister(); final Serializable id = entityEntry.getId(); @@ -146,10 +149,6 @@ public final class TwoPhaseLoad { final SessionImplementor session, final PreLoadEvent preLoadEvent, final PostLoadEvent postLoadEvent) throws HibernateException { - if ( entityEntry == null ) { - throw new AssertionFailure( "possible non-threadsafe access to the session" ); - } - final PersistenceContext persistenceContext = session.getPersistenceContext(); EntityPersister persister = entityEntry.getPersister(); Serializable id = entityEntry.getId(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/AbstractLobCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/AbstractLobCreator.java index 2150d73c62..03c9394e46 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/AbstractLobCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/AbstractLobCreator.java @@ -24,6 +24,7 @@ package org.hibernate.engine.jdbc; import java.sql.Blob; import java.sql.Clob; +import java.sql.NClob; /** * Convenient base class for proxy-based LobCreator for handling wrapping. @@ -38,11 +39,16 @@ public abstract class AbstractLobCreator implements LobCreator { @Override public Clob wrap(Clob clob) { - if ( SerializableNClobProxy.isNClob( clob ) ) { - return SerializableNClobProxy.generateProxy( clob ); + if ( NClob.class.isInstance( clob ) ) { + return wrap( (NClob) clob ); } else { return SerializableClobProxy.generateProxy( clob ); } } + + @Override + public NClob wrap(NClob nclob) { + return SerializableNClobProxy.generateProxy( nclob ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java index 0bc01a7eee..3475b2310c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java @@ -32,6 +32,7 @@ import java.sql.Blob; import java.sql.SQLException; import org.hibernate.engine.jdbc.internal.BinaryStreamImpl; +import org.hibernate.internal.util.ClassLoaderHelper; import org.hibernate.type.descriptor.java.DataHelper; /** @@ -90,8 +91,10 @@ public class BlobProxy implements InvocationHandler { /** * {@inheritDoc} * - * @throws UnsupportedOperationException if any methods other than {@link Blob#length()} - * or {@link Blob#getBinaryStream} are invoked. + * @throws UnsupportedOperationException if any methods other than + * {@link Blob#length}, {@link Blob#getUnderlyingStream}, + * {@link Blob#getBinaryStream}, {@link Blob#getBytes}, {@link Blob#free}, + * or toString/equals/hashCode are invoked. */ @Override @SuppressWarnings({ "UnnecessaryBoxing" }) @@ -194,7 +197,7 @@ public class BlobProxy implements InvocationHandler { * @return The class loader appropriate for proxy construction. */ private static ClassLoader getProxyClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ClassLoader cl = ClassLoaderHelper.getContextClassLoader(); if ( cl == null ) { cl = BlobImplementer.class.getClassLoader(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java index 0ddc9455d3..f45489f2d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java @@ -34,6 +34,7 @@ import java.sql.Clob; import java.sql.SQLException; import org.hibernate.engine.jdbc.internal.CharacterStreamImpl; +import org.hibernate.internal.util.ClassLoaderHelper; import org.hibernate.type.descriptor.java.DataHelper; /** @@ -215,7 +216,7 @@ public class ClobProxy implements InvocationHandler { * @return The class loader appropriate for proxy construction. */ protected static ClassLoader getProxyClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ClassLoader cl = ClassLoaderHelper.getContextClassLoader(); if ( cl == null ) { cl = ClobImplementer.class.getClassLoader(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ColumnNameCache.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ColumnNameCache.java index a1770510ac..934e2c6c55 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ColumnNameCache.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ColumnNameCache.java @@ -43,9 +43,9 @@ public class ColumnNameCache { } public int getIndexForColumnName(String columnName, ResultSet rs) throws SQLException { - Integer cached = ( Integer ) columnNameToIndexCache.get( columnName ); + Integer cached = columnNameToIndexCache.get( columnName ); if ( cached != null ) { - return cached.intValue(); + return cached; } else { int index = rs.findColumn( columnName ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreator.java index b1f6d39628..e96fe27232 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreator.java @@ -51,6 +51,14 @@ public interface LobCreator { */ public Clob wrap(Clob clob); + /** + * Wrap the given nclob in a serializable wrapper. + * + * @param nclob The nclob to be wrapped. + * @return The wrapped nclob which will be castable to {@link NClob} as well as {@link WrappedNClob}. + */ + public NClob wrap(NClob nclob); + /** * Create a BLOB reference encapsulating the given byte array. * diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java index 715f57e767..4f9227891d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java @@ -28,6 +28,8 @@ import java.lang.reflect.Proxy; import java.sql.Clob; import java.sql.NClob; +import org.hibernate.internal.util.ClassLoaderHelper; + /** * Manages aspects of proxying java.sql.NClobs for non-contextual creation, including proxy creation and * handling proxy invocations. We use proxies here solely to avoid JDBC version incompatibilities. @@ -86,7 +88,7 @@ public class NClobProxy extends ClobProxy { * @return The class loader appropriate for proxy construction. */ protected static ClassLoader getProxyClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ClassLoader cl = ClassLoaderHelper.getContextClassLoader(); if ( cl == null ) { cl = NClobImplementer.class.getClassLoader(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java index e2cb8773a2..4b0b21ea3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ResultSetWrapperProxy.java @@ -34,6 +34,7 @@ import org.jboss.logging.Logger; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ClassLoaderHelper; /** * A proxy for a ResultSet delegate, responsible for locally caching the columnName-to-columnIndex resolution that @@ -78,7 +79,7 @@ public class ResultSetWrapperProxy implements InvocationHandler { * @return The class loader appropriate for proxy construction. */ public static ClassLoader getProxyClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ClassLoader cl = ClassLoaderHelper.getContextClassLoader(); if ( cl == null ) { cl = ResultSet.class.getClassLoader(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java index c7f16de4a7..0d8209ef72 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java @@ -31,6 +31,7 @@ import java.lang.reflect.Proxy; import java.sql.Blob; import org.hibernate.HibernateException; +import org.hibernate.internal.util.ClassLoaderHelper; /** * Manages aspects of proxying {@link Blob Blobs} to add serializability. @@ -101,7 +102,7 @@ public class SerializableBlobProxy implements InvocationHandler, Serializable { * @return The class loader appropriate for proxy construction. */ public static ClassLoader getProxyClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ClassLoader cl = ClassLoaderHelper.getContextClassLoader(); if ( cl == null ) { cl = WrappedBlob.class.getClassLoader(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java index a092ff710a..8b499e5c25 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableClobProxy.java @@ -31,6 +31,7 @@ import java.lang.reflect.Proxy; import java.sql.Clob; import org.hibernate.HibernateException; +import org.hibernate.internal.util.ClassLoaderHelper; /** * Manages aspects of proxying {@link Clob Clobs} to add serializability. @@ -100,7 +101,7 @@ public class SerializableClobProxy implements InvocationHandler, Serializable { * @return The class loader appropriate for proxy construction. */ public static ClassLoader getProxyClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ClassLoader cl = ClassLoaderHelper.getContextClassLoader(); if ( cl == null ) { cl = WrappedClob.class.getClassLoader(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableNClobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableNClobProxy.java index 2314f92d3a..cde389cb01 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableNClobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableNClobProxy.java @@ -25,6 +25,7 @@ package org.hibernate.engine.jdbc; import java.lang.reflect.Proxy; import java.sql.Clob; +import java.sql.NClob; /** * Manages aspects of proxying java.sql.NClobs to add serializability. @@ -32,27 +33,10 @@ import java.sql.Clob; * @author Steve Ebersole */ public class SerializableNClobProxy extends SerializableClobProxy { - private static final Class NCLOB_CLASS = loadNClobClassIfAvailable(); - - private static Class loadNClobClassIfAvailable() { - try { - return getProxyClassLoader().loadClass( "java.sql.NClob" ); - } - catch ( ClassNotFoundException e ) { - return null; - } - } - - private static final Class[] PROXY_INTERFACES = new Class[] { determineNClobInterface(), WrappedClob.class }; - - private static Class determineNClobInterface() { - // java.sql.NClob is a simple marker interface extending java.sql.Clob. So if java.sql.NClob is not available - // on the classloader, just use java.sql.Clob - return NCLOB_CLASS == null ? Clob.class : NCLOB_CLASS; - } + private static final Class[] PROXY_INTERFACES = new Class[] { NClob.class, WrappedNClob.class }; public static boolean isNClob(Clob clob) { - return NCLOB_CLASS != null && NCLOB_CLASS.isInstance( clob ); + return NClob.class.isInstance( clob ); } /** @@ -67,16 +51,16 @@ public class SerializableNClobProxy extends SerializableClobProxy { } /** - * Generates a SerializableClobProxy proxy wrapping the provided Clob object. + * Generates a SerializableNClobProxy proxy wrapping the provided NClob object. * - * @param clob The Clob to wrap. + * @param nclob The NClob to wrap. * @return The generated proxy. */ - public static Clob generateProxy(Clob clob) { - return ( Clob ) Proxy.newProxyInstance( + public static NClob generateProxy(NClob nclob) { + return ( NClob ) Proxy.newProxyInstance( getProxyClassLoader(), PROXY_INTERFACES, - new SerializableNClobProxy( clob ) + new SerializableNClobProxy( nclob ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/WrappedNClob.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/WrappedNClob.java new file mode 100644 index 0000000000..41c801e160 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/WrappedNClob.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc; + +import java.sql.NClob; + +/** + * @author Steve Ebersole + */ +public interface WrappedNClob extends WrappedClob { + @Override + @Deprecated + public NClob getWrappedClob(); + + public NClob getWrappedNClob(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java old mode 100755 new mode 100644 index 1edeab7fd4..4d18551cde --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java @@ -161,7 +161,7 @@ public abstract class AbstractBatchImpl implements Batch { for ( PreparedStatement statement : getStatements().values() ) { try { statement.clearBatch(); - statement.close(); + jdbcCoordinator.release( statement ); } catch ( SQLException e ) { LOG.unableToReleaseBatchStatement(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java index 087a7bd08e..04e46a7e44 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java @@ -27,11 +27,10 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Map; -import org.jboss.logging.Logger; - import org.hibernate.engine.jdbc.batch.spi.BatchKey; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.internal.CoreMessageLogger; +import org.jboss.logging.Logger; /** * An implementation of {@link org.hibernate.engine.jdbc.batch.spi.Batch} which does not perform batching. It simply @@ -43,8 +42,11 @@ public class NonBatchingBatch extends AbstractBatchImpl { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, NonBatchingBatch.class.getName() ); + private JdbcCoordinator jdbcCoordinator; + protected NonBatchingBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { super( key, jdbcCoordinator ); + this.jdbcCoordinator = jdbcCoordinator; } @Override @@ -53,14 +55,9 @@ public class NonBatchingBatch extends AbstractBatchImpl { for ( Map.Entry entry : getStatements().entrySet() ) { try { final PreparedStatement statement = entry.getValue(); - final int rowCount = statement.executeUpdate(); + final int rowCount = jdbcCoordinator.getResultSetReturn().executeUpdate( statement ); getKey().getExpectation().verifyOutcome( rowCount, statement, 0 ); - try { - statement.close(); - } - catch (SQLException e) { - LOG.debug( "Unable to close non-batched batch statement", e ); - } + jdbcCoordinator.release( statement ); } catch ( SQLException e ) { LOG.debug( "SQLException escaped proxy", e ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index 0d0c160ff9..1ff78d393e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -24,6 +24,7 @@ package org.hibernate.engine.jdbc.connections.internal; import java.sql.Connection; +import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; @@ -31,9 +32,9 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; -import org.jboss.logging.Logger; - import org.hibernate.HibernateException; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; @@ -41,12 +42,11 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.UnknownUnwrapTypeException; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.service.spi.Configurable; import org.hibernate.service.spi.ServiceRegistryAwareService; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.Stoppable; +import org.jboss.logging.Logger; /** * A connection provider that uses the {@link java.sql.DriverManager} directly to open connections and provides @@ -77,6 +77,8 @@ public class DriverManagerConnectionProviderImpl private boolean stopped; private transient ServiceRegistryImplementor serviceRegistry; + + private Driver driver; @Override public boolean isUnwrappableAs(Class unwrapType) { @@ -105,12 +107,14 @@ public class DriverManagerConnectionProviderImpl } else if ( serviceRegistry != null ) { try { - serviceRegistry.getService( ClassLoaderService.class ).classForName( driverClassName ); + driver = (Driver) serviceRegistry.getService( + ClassLoaderService.class ).classForName( driverClassName ) + .newInstance(); } - catch ( ClassLoadingException e ) { + catch ( Exception e ) { throw new ClassLoadingException( - "Specified JDBC Driver " + driverClassName + " class not found", - e + "Specified JDBC Driver " + driverClassName + + " could not be loaded", e ); } } @@ -118,14 +122,14 @@ public class DriverManagerConnectionProviderImpl else { try { // trying via forName() first to be as close to DriverManager's semantics - Class.forName( driverClassName ); + driver = (Driver) Class.forName( driverClassName ).newInstance(); } - catch ( ClassNotFoundException cnfe ) { + catch ( Exception e1 ) { try{ - ReflectHelper.classForName( driverClassName ); + driver = (Driver) ReflectHelper.classForName( driverClassName ).newInstance(); } - catch ( ClassNotFoundException e ) { - throw new HibernateException( "Specified JDBC Driver " + driverClassName + " class not found", e ); + catch ( Exception e2 ) { + throw new HibernateException( "Specified JDBC Driver " + driverClassName + " could not be loaded", e2 ); } } } @@ -193,9 +197,22 @@ public class DriverManagerConnectionProviderImpl } // otherwise we open a new connection... + final boolean debugEnabled = LOG.isDebugEnabled(); if ( debugEnabled ) LOG.debug( "Opening new JDBC connection" ); - - Connection conn = DriverManager.getConnection( url, connectionProps ); + + Connection conn; + if ( driver != null ) { + // If a Driver is available, completely circumvent + // DriverManager#getConnection. It attempts to double check + // ClassLoaders before using a Driver. This does not work well in + // OSGi environments without wonky workarounds. + conn = driver.connect( url, connectionProps ); + } + else { + // If no Driver, fall back on the original method. + conn = DriverManager.getConnection( url, connectionProps ); + } + if ( isolation != null ) { conn.setTransactionIsolation( isolation.intValue() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java index 93f3e6a9de..3e83be53c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/internal/StandardRefCursorSupport.java @@ -192,7 +192,7 @@ public class StandardRefCursorSupport implements RefCursorSupport { throw new HibernateException( "Unexpected error trying to determine REF_CURSOR field value : " + e.getMessage() ); } } - return refCursorTypeCode.intValue(); + return refCursorTypeCode; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/BasicDialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/BasicDialectResolver.java index 79cd856ef3..fb617d72c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/BasicDialectResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/BasicDialectResolver.java @@ -28,15 +28,18 @@ import java.sql.SQLException; import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.spi.AbstractDatabaseMetaDataDialectResolver; /** * Intended as support for custom resolvers. * * @author Steve Ebersole + * + * @deprecated Purpose has shifted to new {@link org.hibernate.engine.jdbc.dialect.spi.DatabaseInfoDialectResolver} + * contract. See HHH-7965 for details. */ -public class BasicDialectResolver extends AbstractDialectResolver { - // TODO: should this disappear??? - +@Deprecated +public class BasicDialectResolver extends AbstractDatabaseMetaDataDialectResolver { public static final int VERSION_INSENSITIVE_VERSION = -9999; private final String matchingName; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DatabaseInfoDialectResolverInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DatabaseInfoDialectResolverInitiator.java new file mode 100644 index 0000000000..9ad43d9ca2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DatabaseInfoDialectResolverInitiator.java @@ -0,0 +1,79 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc.dialect.internal; + +import java.util.Map; + +import org.hibernate.HibernateException; +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseInfoDialectResolver; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.service.spi.ServiceException; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * @author Steve Ebersole + */ +public class DatabaseInfoDialectResolverInitiator implements StandardServiceInitiator { + public static final DatabaseInfoDialectResolverInitiator INSTANCE = new DatabaseInfoDialectResolverInitiator(); + + @Override + public Class getServiceInitiated() { + return DatabaseInfoDialectResolver.class; + } + + @Override + public DatabaseInfoDialectResolver initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + final DatabaseInfoDialectResolverSet resolver = new DatabaseInfoDialectResolverSet(); + applyCustomReslvers( resolver, configurationValues, registry ); + resolver.addResolver( StandardDatabaseInfoDialectResolver.INSTANCE ); + return resolver; + } + + private void applyCustomReslvers( + DatabaseInfoDialectResolverSet resolver, + Map configurationValues, + ServiceRegistryImplementor registry) { + final String resolverImplNames = (String) configurationValues.get( AvailableSettings.DIALECT_RESOLVERS ); + + if ( StringHelper.isNotEmpty( resolverImplNames ) ) { + final ClassLoaderService classLoaderService = registry.getService( ClassLoaderService.class ); + for ( String resolverImplName : StringHelper.split( ", \n\r\f\t", resolverImplNames ) ) { + try { + resolver.addResolver( + (DatabaseInfoDialectResolver) classLoaderService.classForName( resolverImplName ).newInstance() + ); + } + catch (HibernateException e) { + throw e; + } + catch (Exception e) { + throw new ServiceException( "Unable to instantiate named dialect resolver [" + resolverImplName + "]", e ); + } + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DatabaseInfoDialectResolverSet.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DatabaseInfoDialectResolverSet.java new file mode 100644 index 0000000000..d3816ccd69 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DatabaseInfoDialectResolverSet.java @@ -0,0 +1,81 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc.dialect.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseInfoDialectResolver; + +/** + * @author Steve Ebersole + */ +public class DatabaseInfoDialectResolverSet implements DatabaseInfoDialectResolver { + private List delegateResolvers; + + public DatabaseInfoDialectResolverSet() { + this( new ArrayList() ); + } + + public DatabaseInfoDialectResolverSet(List delegateResolvers) { + this.delegateResolvers = delegateResolvers; + } + + public DatabaseInfoDialectResolverSet(DatabaseInfoDialectResolver... delegateResolvers) { + this( Arrays.asList( delegateResolvers ) ); + } + + @Override + public Dialect resolve(DatabaseInfo databaseInfo) { + for ( DatabaseInfoDialectResolver resolver : delegateResolvers ) { + Dialect dialect = resolver.resolve( databaseInfo ); + if ( dialect != null ) { + return dialect; + } + } + return null; + } + + /** + * Add a resolver at the end of the underlying resolver list. The resolver added by this method is at lower + * priority than any other existing resolvers. + * + * @param resolver The resolver to add. + */ + public void addResolver(DatabaseInfoDialectResolver resolver) { + delegateResolvers.add( resolver ); + } + + /** + * Add a resolver at the beginning of the underlying resolver list. The resolver added by this method is at higher + * priority than any other existing resolvers. + * + * @param resolver The resolver to add. + */ + public void addResolverAtFirst(DatabaseInfoDialectResolver resolver) { + delegateResolvers.add( 0, resolver ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DialectResolverInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DialectResolverInitiator.java index bd48a88630..582d095a13 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DialectResolverInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/DialectResolverInitiator.java @@ -23,16 +23,15 @@ */ package org.hibernate.engine.jdbc.dialect.internal; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import org.hibernate.HibernateException; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseInfoDialectResolver; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; +import org.hibernate.internal.util.StringHelper; import org.hibernate.service.spi.ServiceException; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -51,19 +50,29 @@ public class DialectResolverInitiator implements StandardServiceInitiator determineResolvers(Map configurationValues, ServiceRegistryImplementor registry) { - final List resolvers = new ArrayList(); - + private void applyCustomerResolvers( + DialectResolverSet resolver, + ServiceRegistryImplementor registry, + Map configurationValues) { final String resolverImplNames = (String) configurationValues.get( AvailableSettings.DIALECT_RESOLVERS ); if ( StringHelper.isNotEmpty( resolverImplNames ) ) { final ClassLoaderService classLoaderService = registry.getService( ClassLoaderService.class ); for ( String resolverImplName : StringHelper.split( ", \n\r\f\t", resolverImplNames ) ) { try { - resolvers.add( (DialectResolver) classLoaderService.classForName( resolverImplName ).newInstance() ); + resolver.addResolver( + (DialectResolver) classLoaderService.classForName( resolverImplName ).newInstance() + ); } catch (HibernateException e) { throw e; @@ -73,8 +82,5 @@ public class DialectResolverInitiator implements StandardServiceInitiator 8 || ( databaseMajorVersion == 8 && databaseMinorVersion >= 2 ) ) { + final int majorVersion = databaseInfo.getDatabaseMajorVersion(); + final int minorVersion = databaseInfo.getDatabaseMinorVersion(); + + if ( majorVersion > 8 || ( majorVersion == 8 && minorVersion >= 2 ) ) { return new PostgreSQL82Dialect(); } return new PostgreSQL81Dialect(); } if ( "Apache Derby".equals( databaseName ) ) { - final int databaseMinorVersion = metaData.getDatabaseMinorVersion(); - if ( databaseMajorVersion > 10 || ( databaseMajorVersion == 10 && databaseMinorVersion >= 7 ) ) { + final int majorVersion = databaseInfo.getDatabaseMajorVersion(); + final int minorVersion = databaseInfo.getDatabaseMinorVersion(); + + if ( majorVersion > 10 || ( majorVersion == 10 && minorVersion >= 7 ) ) { return new DerbyTenSevenDialect(); } - else if ( databaseMajorVersion == 10 && databaseMinorVersion == 6 ) { + else if ( majorVersion == 10 && minorVersion == 6 ) { return new DerbyTenSixDialect(); } - else if ( databaseMajorVersion == 10 && databaseMinorVersion == 5 ) { + else if ( majorVersion == 10 && minorVersion == 5 ) { return new DerbyTenFiveDialect(); } else { @@ -111,32 +113,36 @@ public class StandardDialectResolver extends AbstractDialectResolver { } if ( "ingres".equalsIgnoreCase( databaseName ) ) { - switch ( databaseMajorVersion ) { - case 9: - int databaseMinorVersion = metaData.getDatabaseMinorVersion(); - if (databaseMinorVersion > 2) { - return new Ingres9Dialect(); - } - return new IngresDialect(); - case 10: - return new Ingres10Dialect(); - default: - LOG.unknownIngresVersion(databaseMajorVersion); - } + final int majorVersion = databaseInfo.getDatabaseMajorVersion(); + final int minorVersion = databaseInfo.getDatabaseMinorVersion(); + + switch ( majorVersion ) { + case 9: + if (minorVersion > 2) { + return new Ingres9Dialect(); + } + return new IngresDialect(); + case 10: + return new Ingres10Dialect(); + default: + LOG.unknownIngresVersion( majorVersion ); + } return new IngresDialect(); } if ( databaseName.startsWith( "Microsoft SQL Server" ) ) { - switch ( databaseMajorVersion ) { - case 8: - return new SQLServerDialect(); - case 9: - return new SQLServer2005Dialect(); - case 10: - case 11: - return new SQLServer2008Dialect(); - default: - LOG.unknownSqlServerVersion(databaseMajorVersion); + final int majorVersion = databaseInfo.getDatabaseMajorVersion(); + + switch ( majorVersion ) { + case 8: + return new SQLServerDialect(); + case 9: + return new SQLServer2005Dialect(); + case 10: + case 11: + return new SQLServer2008Dialect(); + default: + LOG.unknownSqlServerVersion( majorVersion ); } return new SQLServerDialect(); } @@ -152,7 +158,7 @@ public class StandardDialectResolver extends AbstractDialectResolver { if ( "Informix Dynamic Server".equals( databaseName ) ) { return new InformixDialect(); } - + if ( databaseName.equals("DB2 UDB for AS/400" ) ) { return new DB2400Dialect(); } @@ -162,7 +168,9 @@ public class StandardDialectResolver extends AbstractDialectResolver { } if ( "Oracle".equals( databaseName ) ) { - switch ( databaseMajorVersion ) { + final int majorVersion = databaseInfo.getDatabaseMajorVersion(); + + switch ( majorVersion ) { case 11: return new Oracle10gDialect(); case 10: @@ -172,7 +180,7 @@ public class StandardDialectResolver extends AbstractDialectResolver { case 8: return new Oracle8iDialect(); default: - LOG.unknownOracleVersion(databaseMajorVersion); + LOG.unknownOracleVersion( majorVersion ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/StandardDatabaseMetaDataDialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/StandardDatabaseMetaDataDialectResolver.java new file mode 100644 index 0000000000..2cf8b5cd97 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/StandardDatabaseMetaDataDialectResolver.java @@ -0,0 +1,92 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc.dialect.internal; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.resolver.BasicSQLExceptionConverter; +import org.hibernate.engine.jdbc.dialect.spi.AbstractDatabaseMetaDataDialectResolver; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseInfoDialectResolver; + +/** + * The standard Hibernate Dialect resolver. + * + * @author Steve Ebersole + */ +public class StandardDatabaseMetaDataDialectResolver extends AbstractDatabaseMetaDataDialectResolver { + private final DatabaseInfoDialectResolver infoResolver; + + public StandardDatabaseMetaDataDialectResolver(DatabaseInfoDialectResolver infoResolver) { + this.infoResolver = infoResolver; + } + + public static final class DatabaseInfoImpl implements DatabaseInfoDialectResolver.DatabaseInfo { + private final DatabaseMetaData databaseMetaData; + + public DatabaseInfoImpl(DatabaseMetaData databaseMetaData) { + this.databaseMetaData = databaseMetaData; + } + + @Override + public String getDatabaseName() { + try { + return databaseMetaData.getDatabaseProductName(); + } + catch (SQLException e) { + throw BasicSQLExceptionConverter.INSTANCE.convert( e ); + } + } + + @Override + public int getDatabaseMajorVersion() { + try { + return databaseMetaData.getDatabaseMajorVersion(); + } + catch (SQLException e) { + throw BasicSQLExceptionConverter.INSTANCE.convert( e ); + } + } + + @Override + public int getDatabaseMinorVersion() { + try { + return databaseMetaData.getDatabaseMinorVersion(); + } + catch (SQLException e) { + throw BasicSQLExceptionConverter.INSTANCE.convert( e ); + } + } + } + + @Override + protected Dialect resolveDialectInternal(DatabaseMetaData metaData) throws SQLException { + if ( infoResolver == null ) { + return null; + } + + return infoResolver.resolve( new DatabaseInfoImpl( metaData ) ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/AbstractDialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/AbstractDatabaseMetaDataDialectResolver.java similarity index 83% rename from hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/AbstractDialectResolver.java rename to hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/AbstractDatabaseMetaDataDialectResolver.java index 0d50ba37cf..b6db70494a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/internal/AbstractDialectResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/AbstractDatabaseMetaDataDialectResolver.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.engine.jdbc.dialect.internal; +package org.hibernate.engine.jdbc.dialect.spi; import java.sql.DatabaseMetaData; import java.sql.SQLException; @@ -33,7 +33,6 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.resolver.BasicSQLExceptionConverter; import org.hibernate.exception.JDBCConnectionException; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; /** * A templated resolver impl which delegates to the {@link #resolveDialectInternal} method @@ -41,10 +40,12 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; * * @author Steve Ebersole */ -public abstract class AbstractDialectResolver implements DialectResolver { +public abstract class AbstractDatabaseMetaDataDialectResolver implements DialectResolver { - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, - AbstractDialectResolver.class.getName()); + private static final CoreMessageLogger LOG = Logger.getMessageLogger( + CoreMessageLogger.class, + AbstractDatabaseMetaDataDialectResolver.class.getName() + ); /** * {@inheritDoc} @@ -58,12 +59,15 @@ public abstract class AbstractDialectResolver implements DialectResolver { } catch ( SQLException sqlException ) { JDBCException jdbcException = BasicSQLExceptionConverter.INSTANCE.convert( sqlException ); - if (jdbcException instanceof JDBCConnectionException) throw jdbcException; - LOG.warnf("%s : %s", BasicSQLExceptionConverter.MSG, sqlException.getMessage()); + if (jdbcException instanceof JDBCConnectionException) { + throw jdbcException; + } + + LOG.warnf( "%s : %s", BasicSQLExceptionConverter.MSG, sqlException.getMessage() ); return null; } catch ( Throwable t ) { - LOG.unableToExecuteResolver(this, t.getMessage()); + LOG.unableToExecuteResolver( this, t.getMessage() ); return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseInfoDialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseInfoDialectResolver.java new file mode 100644 index 0000000000..9eccc26347 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseInfoDialectResolver.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc.dialect.spi; + +import org.hibernate.dialect.Dialect; +import org.hibernate.service.Service; + +/** + * A contract for resolving database name, major version and minor version to Dialect + * + * @author Steve Ebersole + */ +public interface DatabaseInfoDialectResolver extends Service { + /** + * Determine the {@link Dialect} to use based on the given information. Implementations are + * expected to return the {@link Dialect} instance to use, or {@code null} if the they did not locate a match. + * + * @param databaseInfo Access to the needed database information + * + * @return The dialect to use, or null. + */ + public Dialect resolve(DatabaseInfo databaseInfo); + + public static interface DatabaseInfo { + public static final int NO_VERSION = -9999; + + /** + * Obtain access to the database name, as returned from {@link java.sql.DatabaseMetaData#getDatabaseProductName()} + * for the target database + * + * @return The database name + */ + public String getDatabaseName(); + + /** + * Obtain access to the database major version, as returned from + * {@link java.sql.DatabaseMetaData#getDatabaseMajorVersion()} for the target database; {@value #NO_VERSION} + * indicates no version information was supplied + * + * @return The major version + * + * @see #NO_VERSION + */ + public int getDatabaseMajorVersion(); + + /** + * Obtain access to the database minor version, as returned from + * {@link java.sql.DatabaseMetaData#getDatabaseMinorVersion()} for the target database; {@value #NO_VERSION} + * indicates no version information was supplied + * + * @return The minor version + * + * @see #NO_VERSION + */ + public int getDatabaseMinorVersion(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseMetaDataDialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseMetaDataDialectResolver.java new file mode 100644 index 0000000000..e9ee437b55 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseMetaDataDialectResolver.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc.dialect.spi; + +import java.sql.DatabaseMetaData; + +import org.hibernate.dialect.Dialect; +import org.hibernate.exception.JDBCConnectionException; +import org.hibernate.service.Service; + +/** + * Contract for determining the {@link Dialect} to use based on a JDBC {@link java.sql.Connection}. + * + * @author Tomoto Shimizu Washio + * @author Steve Ebersole + */ +public interface DatabaseMetaDataDialectResolver extends Service { + /** + * Determine the {@link org.hibernate.dialect.Dialect} to use based on the given JDBC {@link java.sql.DatabaseMetaData}. Implementations are + * expected to return the {@link org.hibernate.dialect.Dialect} instance to use, or null if the {@link java.sql.DatabaseMetaData} does not match + * the criteria handled by this impl. + * + * @param metaData The JDBC metadata. + * + * @return The dialect to use, or null. + * + * @throws org.hibernate.exception.JDBCConnectionException Indicates a 'non transient connection problem', which indicates that + * we should stop resolution attempts. + */ + public Dialect resolveDialect(DatabaseMetaData metaData) throws JDBCConnectionException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DialectResolver.java index 67ef46b4b2..4cec52fa7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DialectResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DialectResolver.java @@ -23,30 +23,10 @@ */ package org.hibernate.engine.jdbc.dialect.spi; -import java.sql.DatabaseMetaData; - -import org.hibernate.dialect.Dialect; -import org.hibernate.exception.JDBCConnectionException; -import org.hibernate.service.Service; - /** - * Contract for determining the {@link Dialect} to use based on a JDBC {@link java.sql.Connection}. - * - * @author Tomoto Shimizu Washio - * @author Steve Ebersole + * @deprecated Deprecated in favor of {@link DatabaseMetaDataDialectResolver} to account for resolving by name versus + * by DatabaseMetaData */ -public interface DialectResolver extends Service { - /** - * Determine the {@link Dialect} to use based on the given JDBC {@link DatabaseMetaData}. Implementations are - * expected to return the {@link Dialect} instance to use, or null if the {@link DatabaseMetaData} does not match - * the criteria handled by this impl. - * - * @param metaData The JDBC metadata. - * - * @return The dialect to use, or null. - * - * @throws JDBCConnectionException Indicates a 'non transient connection problem', which indicates that - * we should stop resolution attempts. - */ - public Dialect resolveDialect(DatabaseMetaData metaData) throws JDBCConnectionException; +@Deprecated +public interface DialectResolver extends DatabaseMetaDataDialectResolver { } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java index 38e07eb0f6..0b2babef47 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java @@ -336,7 +336,7 @@ public class BasicFormatterImpl implements Formatter { parensSinceSelect--; if ( parensSinceSelect < 0 ) { indent--; - parensSinceSelect = parenCounts.removeLast().intValue(); + parensSinceSelect = parenCounts.removeLast(); afterByOrSetOrFromOrSelect = afterByOrFromOrSelects.removeLast().booleanValue(); } if ( inFunction > 0 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java index c5c2da7c0d..a2c848be24 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java @@ -27,17 +27,25 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; -import org.jboss.logging.Logger; - +import org.hibernate.ConnectionReleaseMode; import org.hibernate.HibernateException; import org.hibernate.TransactionException; import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.batch.spi.BatchBuilder; import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.InvalidatableWrapper; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.jdbc.spi.JdbcWrapper; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; +import org.hibernate.engine.jdbc.spi.ResultSetReturn; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.StatementPreparer; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -48,6 +56,8 @@ import org.hibernate.engine.transaction.spi.TransactionEnvironment; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jdbc.WorkExecutor; import org.hibernate.jdbc.WorkExecutorVisitable; +import org.jboss.logging.Logger; +import org.jboss.logging.Logger.Level; /** * Standard Hibernate implementation of {@link JdbcCoordinator} @@ -55,22 +65,34 @@ import org.hibernate.jdbc.WorkExecutorVisitable; * IMPL NOTE : Custom serialization handling! * * @author Steve Ebersole + * @author Brett Meyer */ public class JdbcCoordinatorImpl implements JdbcCoordinator { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, JdbcCoordinatorImpl.class.getName() ); - private transient TransactionCoordinatorImpl transactionCoordinator; + private transient TransactionCoordinator transactionCoordinator; private final transient LogicalConnectionImpl logicalConnection; private transient Batch currentBatch; private transient long transactionTimeOutInstant = -1; + private final HashMap> xref = new HashMap>(); + private final Set unassociatedResultSets = new HashSet(); + private final SqlExceptionHelper exceptionHelper; + + private Statement lastQuery; + + /** + * If true, manually (and temporarily) circumvent aggressive release processing. + */ + private boolean releasesEnabled = true; + public JdbcCoordinatorImpl( Connection userSuppliedConnection, - TransactionCoordinatorImpl transactionCoordinator) { + TransactionCoordinator transactionCoordinator) { this.transactionCoordinator = transactionCoordinator; this.logicalConnection = new LogicalConnectionImpl( userSuppliedConnection, @@ -78,10 +100,20 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { transactionCoordinator.getTransactionContext().getTransactionEnvironment().getJdbcServices(), transactionCoordinator.getTransactionContext().getJdbcConnectionAccess() ); + this.exceptionHelper = logicalConnection.getJdbcServices().getSqlExceptionHelper(); + } + + public JdbcCoordinatorImpl( + LogicalConnectionImpl logicalConnection, + TransactionCoordinator transactionCoordinator) { + this.transactionCoordinator = transactionCoordinator; + this.logicalConnection = logicalConnection; + this.exceptionHelper = logicalConnection.getJdbcServices().getSqlExceptionHelper(); } private JdbcCoordinatorImpl(LogicalConnectionImpl logicalConnection) { this.logicalConnection = logicalConnection; + this.exceptionHelper = logicalConnection.getJdbcServices().getSqlExceptionHelper(); } @Override @@ -106,7 +138,7 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { return sessionFactory().getServiceRegistry().getService( BatchBuilder.class ); } - private SqlExceptionHelper sqlExceptionHelper() { + public SqlExceptionHelper sqlExceptionHelper() { return transactionEnvironment().getJdbcServices().getSqlExceptionHelper(); } @@ -116,7 +148,7 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { @Override public void flushBeginning() { if ( flushDepth == 0 ) { - logicalConnection.disableReleases(); + releasesEnabled = false; } flushDepth++; } @@ -128,16 +160,20 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { throw new HibernateException( "Mismatched flush handling" ); } if ( flushDepth == 0 ) { - logicalConnection.enableReleases(); + releasesEnabled = true; } + + afterStatementExecution(); } @Override public Connection close() { + LOG.tracev( "Closing JDBC container [{0}]", this ); if ( currentBatch != null ) { LOG.closingUnreleasedBatch(); currentBatch.release(); } + cleanup(); return logicalConnection.close(); } @@ -181,6 +217,16 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { return statementPreparer; } + private transient ResultSetReturn resultSetExtractor; + + @Override + public ResultSetReturn getResultSetReturn() { + if ( resultSetExtractor == null ) { + resultSetExtractor = new ResultSetReturnImpl( this ); + } + return resultSetExtractor; + } + @Override public void setTransactionTimeOut(int seconds) { transactionTimeOutInstant = System.currentTimeMillis() + ( seconds * 1000 ); @@ -198,43 +244,61 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { return secondsRemaining; } + @Override + public void afterStatementExecution() { + LOG.tracev( "Starting after statement execution processing [{0}]", connectionReleaseMode() ); + if ( connectionReleaseMode() == ConnectionReleaseMode.AFTER_STATEMENT ) { + if ( ! releasesEnabled ) { + LOG.debug( "Skipping aggressive release due to manual disabling" ); + return; + } + if ( hasRegisteredResources() ) { + LOG.debug( "Skipping aggressive release due to registered resources" ); + return; + } + getLogicalConnection().releaseConnection(); + } + } + @Override public void afterTransaction() { - logicalConnection.afterTransaction(); transactionTimeOutInstant = -1; + if ( connectionReleaseMode() == ConnectionReleaseMode.AFTER_STATEMENT || + connectionReleaseMode() == ConnectionReleaseMode.AFTER_TRANSACTION ) { + if ( hasRegisteredResources() ) { + LOG.forcingContainerResourceCleanup(); + releaseResources(); + } + getLogicalConnection().aggressiveRelease(); + } + } + + private ConnectionReleaseMode connectionReleaseMode() { + return getLogicalConnection().getConnectionReleaseMode(); } @Override public T coordinateWork(WorkExecutorVisitable work) { - Connection connection = getLogicalConnection().getDistinctConnectionProxy(); + Connection connection = getLogicalConnection().getConnection(); try { T result = work.accept( new WorkExecutor(), connection ); - getLogicalConnection().afterStatementExecution(); + afterStatementExecution(); return result; } catch ( SQLException e ) { throw sqlExceptionHelper().convert( e, "error executing work" ); } - finally { - try { - if ( ! connection.isClosed() ) { - connection.close(); - } - } - catch (SQLException e) { - LOG.debug( "Error closing connection proxy", e ); - } - } } @Override - public void cancelLastQuery() { - logicalConnection.getResourceRegistry().cancelLastQuery(); + public boolean isReadyForSerialization() { + return getLogicalConnection().isUserSuppliedConnection() + ? ! getLogicalConnection().isPhysicallyConnected() + : ! hasRegisteredResources(); } - public void serialize(ObjectOutputStream oos) throws IOException { - if ( ! logicalConnection.isReadyForSerialization() ) { + if ( ! isReadyForSerialization() ) { throw new HibernateException( "Cannot serialize Session while connected" ); } logicalConnection.serialize( oos ); @@ -249,4 +313,222 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { public void afterDeserialize(TransactionCoordinatorImpl transactionCoordinator) { this.transactionCoordinator = transactionCoordinator; } + + @Override + public void register(Statement statement) { + LOG.tracev( "Registering statement [{0}]", statement ); + if ( xref.containsKey( statement ) ) { + throw new HibernateException( "statement already registered with JDBCContainer" ); + } + xref.put( statement, null ); + } + + @Override + @SuppressWarnings({ "unchecked" }) + public void registerLastQuery(Statement statement) { + LOG.tracev( "Registering last query statement [{0}]", statement ); + if ( statement instanceof JdbcWrapper ) { + JdbcWrapper wrapper = ( JdbcWrapper ) statement; + registerLastQuery( wrapper.getWrappedObject() ); + return; + } + lastQuery = statement; + } + + @Override + public void cancelLastQuery() { + try { + if (lastQuery != null) { + lastQuery.cancel(); + } + } + catch (SQLException sqle) { + throw exceptionHelper.convert( + sqle, + "Cannot cancel query" + ); + } + finally { + lastQuery = null; + } + } + + @Override + public void release(Statement statement) { + LOG.tracev( "Releasing statement [{0}]", statement ); + Set resultSets = xref.get( statement ); + if ( resultSets != null ) { + for ( ResultSet resultSet : resultSets ) { + close( resultSet ); + } + resultSets.clear(); + } + xref.remove( statement ); + close( statement ); + + afterStatementExecution(); + } + + @Override + public void register(ResultSet resultSet) { + LOG.tracev( "Registering result set [{0}]", resultSet ); + Statement statement; + try { + statement = resultSet.getStatement(); + } + catch ( SQLException e ) { + throw exceptionHelper.convert( e, "unable to access statement from resultset" ); + } + if ( statement != null ) { + if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) { + LOG.unregisteredStatement(); + } + Set resultSets = xref.get( statement ); + if ( resultSets == null ) { + resultSets = new HashSet(); + xref.put( statement, resultSets ); + } + resultSets.add( resultSet ); + } + else { + unassociatedResultSets.add( resultSet ); + } + } + + @Override + public void release(ResultSet resultSet) { + LOG.tracev( "Releasing result set [{0}]", resultSet ); + Statement statement; + try { + statement = resultSet.getStatement(); + } + catch ( SQLException e ) { + throw exceptionHelper.convert( e, "unable to access statement from resultset" ); + } + if ( statement != null ) { + if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) { + LOG.unregisteredStatement(); + } + Set resultSets = xref.get( statement ); + if ( resultSets != null ) { + resultSets.remove( resultSet ); + if ( resultSets.isEmpty() ) { + xref.remove( statement ); + } + } + } + else { + boolean removed = unassociatedResultSets.remove( resultSet ); + if ( !removed ) { + LOG.unregisteredResultSetWithoutStatement(); + } + } + close( resultSet ); + } + + @Override + public boolean hasRegisteredResources() { + return ! xref.isEmpty() || ! unassociatedResultSets.isEmpty(); + } + + @Override + public void releaseResources() { + LOG.tracev( "Releasing JDBC container resources [{0}]", this ); + cleanup(); + } + + @Override + public void enableReleases() { + releasesEnabled = true; + } + + @Override + public void disableReleases() { + releasesEnabled = false; + } + + private void cleanup() { + for ( Map.Entry> entry : xref.entrySet() ) { + if ( entry.getValue() != null ) { + closeAll( entry.getValue() ); + } + close( entry.getKey() ); + } + xref.clear(); + + closeAll( unassociatedResultSets ); + } + + protected void closeAll(Set resultSets) { + for ( ResultSet resultSet : resultSets ) { + close( resultSet ); + } + resultSets.clear(); + } + + @SuppressWarnings({ "unchecked" }) + protected void close(Statement statement) { + LOG.tracev( "Closing prepared statement [{0}]", statement ); + + if ( statement instanceof InvalidatableWrapper ) { + InvalidatableWrapper wrapper = ( InvalidatableWrapper ) statement; + close( wrapper.getWrappedObject() ); + wrapper.invalidate(); + return; + } + + try { + // if we are unable to "clean" the prepared statement, + // we do not close it + try { + if ( statement.getMaxRows() != 0 ) { + statement.setMaxRows( 0 ); + } + if ( statement.getQueryTimeout() != 0 ) { + statement.setQueryTimeout( 0 ); + } + } + catch( SQLException sqle ) { + // there was a problem "cleaning" the prepared statement + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Exception clearing maxRows/queryTimeout [%s]", sqle.getMessage() ); + } + return; // EARLY EXIT!!! + } + statement.close(); + if ( lastQuery == statement ) { + lastQuery = null; + } + } + catch( SQLException e ) { + LOG.debugf( "Unable to release JDBC statement [%s]", e.getMessage() ); + } + catch ( Exception e ) { + // try to handle general errors more elegantly + LOG.debugf( "Unable to release JDBC statement [%s]", e.getMessage() ); + } + } + + @SuppressWarnings({ "unchecked" }) + protected void close(ResultSet resultSet) { + LOG.tracev( "Closing result set [{0}]", resultSet ); + + if ( resultSet instanceof InvalidatableWrapper ) { + InvalidatableWrapper wrapper = (InvalidatableWrapper) resultSet; + close( wrapper.getWrappedObject() ); + wrapper.invalidate(); + return; + } + + try { + resultSet.close(); + } + catch( SQLException e ) { + LOG.debugf( "Unable to release JDBC result set [%s]", e.getMessage() ); + } + catch ( Exception e ) { + // try to handle general errors more elegantly + LOG.debugf( "Unable to release JDBC result set [%s]", e.getMessage() ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcResourceRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcResourceRegistryImpl.java deleted file mode 100644 index aa9937d364..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcResourceRegistryImpl.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.jboss.logging.Logger; -import org.jboss.logging.Logger.Level; - -import org.hibernate.HibernateException; -import org.hibernate.engine.jdbc.spi.InvalidatableWrapper; -import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; -import org.hibernate.engine.jdbc.spi.JdbcWrapper; -import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; -import org.hibernate.internal.CoreMessageLogger; - -/** - * Standard implementation of the {@link org.hibernate.engine.jdbc.spi.JdbcResourceRegistry} contract - * - * @author Steve Ebersole - */ -public class JdbcResourceRegistryImpl implements JdbcResourceRegistry { - - private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, JdbcResourceRegistryImpl.class.getName() ); - - private final HashMap> xref = new HashMap>(); - private final Set unassociatedResultSets = new HashSet(); - private final SqlExceptionHelper exceptionHelper; - - private Statement lastQuery; - - public JdbcResourceRegistryImpl(SqlExceptionHelper exceptionHelper) { - this.exceptionHelper = exceptionHelper; - } - - @Override - public void register(Statement statement) { - LOG.tracev( "Registering statement [{0}]", statement ); - if ( xref.containsKey( statement ) ) { - throw new HibernateException( "statement already registered with JDBCContainer" ); - } - xref.put( statement, null ); - } - - @Override - @SuppressWarnings({ "unchecked" }) - public void registerLastQuery(Statement statement) { - LOG.tracev( "Registering last query statement [{0}]", statement ); - if ( statement instanceof JdbcWrapper ) { - JdbcWrapper wrapper = ( JdbcWrapper ) statement; - registerLastQuery( wrapper.getWrappedObject() ); - return; - } - lastQuery = statement; - } - - @Override - public void cancelLastQuery() { - try { - if (lastQuery != null) { - lastQuery.cancel(); - } - } - catch (SQLException sqle) { - throw exceptionHelper.convert( - sqle, - "Cannot cancel query" - ); - } - finally { - lastQuery = null; - } - } - - @Override - public void release(Statement statement) { - LOG.tracev( "Releasing statement [{0}]", statement ); - Set resultSets = xref.get( statement ); - if ( resultSets != null ) { - for ( ResultSet resultSet : resultSets ) { - close( resultSet ); - } - resultSets.clear(); - } - xref.remove( statement ); - close( statement ); - } - - @Override - public void register(ResultSet resultSet) { - LOG.tracev( "Registering result set [{0}]", resultSet ); - Statement statement; - try { - statement = resultSet.getStatement(); - } - catch ( SQLException e ) { - throw exceptionHelper.convert( e, "unable to access statement from resultset" ); - } - if ( statement != null ) { - if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) { - LOG.unregisteredStatement(); - } - Set resultSets = xref.get( statement ); - if ( resultSets == null ) { - resultSets = new HashSet(); - xref.put( statement, resultSets ); - } - resultSets.add( resultSet ); - } - else { - unassociatedResultSets.add( resultSet ); - } - } - - @Override - public void release(ResultSet resultSet) { - LOG.tracev( "Releasing result set [{0}]", resultSet ); - Statement statement; - try { - statement = resultSet.getStatement(); - } - catch ( SQLException e ) { - throw exceptionHelper.convert( e, "unable to access statement from resultset" ); - } - if ( statement != null ) { - if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) { - LOG.unregisteredStatement(); - } - Set resultSets = xref.get( statement ); - if ( resultSets != null ) { - resultSets.remove( resultSet ); - if ( resultSets.isEmpty() ) { - xref.remove( statement ); - } - } - } - else { - boolean removed = unassociatedResultSets.remove( resultSet ); - if ( !removed ) { - LOG.unregisteredResultSetWithoutStatement(); - } - } - close( resultSet ); - } - - @Override - public boolean hasRegisteredResources() { - return ! xref.isEmpty() || ! unassociatedResultSets.isEmpty(); - } - - @Override - public void releaseResources() { - LOG.tracev( "Releasing JDBC container resources [{0}]", this ); - cleanup(); - } - - private void cleanup() { - for ( Map.Entry> entry : xref.entrySet() ) { - if ( entry.getValue() != null ) { - closeAll( entry.getValue() ); - } - close( entry.getKey() ); - } - xref.clear(); - - closeAll( unassociatedResultSets ); - } - - protected void closeAll(Set resultSets) { - for ( ResultSet resultSet : resultSets ) { - close( resultSet ); - } - resultSets.clear(); - } - - @Override - public void close() { - LOG.tracev( "Closing JDBC container [{0}]", this ); - cleanup(); - } - - @SuppressWarnings({ "unchecked" }) - protected void close(Statement statement) { - LOG.tracev( "Closing prepared statement [{0}]", statement ); - - if ( statement instanceof InvalidatableWrapper ) { - InvalidatableWrapper wrapper = ( InvalidatableWrapper ) statement; - close( wrapper.getWrappedObject() ); - wrapper.invalidate(); - return; - } - - try { - // if we are unable to "clean" the prepared statement, - // we do not close it - try { - if ( statement.getMaxRows() != 0 ) { - statement.setMaxRows( 0 ); - } - if ( statement.getQueryTimeout() != 0 ) { - statement.setQueryTimeout( 0 ); - } - } - catch( SQLException sqle ) { - // there was a problem "cleaning" the prepared statement - if ( LOG.isDebugEnabled() ) { - LOG.debugf( "Exception clearing maxRows/queryTimeout [%s]", sqle.getMessage() ); - } - return; // EARLY EXIT!!! - } - statement.close(); - if ( lastQuery == statement ) { - lastQuery = null; - } - } - catch( SQLException e ) { - LOG.debugf( "Unable to release JDBC statement [%s]", e.getMessage() ); - } - catch ( Exception e ) { - // try to handle general errors more elegantly - LOG.debugf( "Unable to release JDBC statement [%s]", e.getMessage() ); - } - } - - @SuppressWarnings({ "unchecked" }) - protected void close(ResultSet resultSet) { - LOG.tracev( "Closing result set [{0}]", resultSet ); - - if ( resultSet instanceof InvalidatableWrapper ) { - InvalidatableWrapper wrapper = (InvalidatableWrapper) resultSet; - close( wrapper.getWrappedObject() ); - wrapper.invalidate(); - return; - } - - try { - resultSet.close(); - } - catch( SQLException e ) { - LOG.debugf( "Unable to release JDBC result set [%s]", e.getMessage() ); - } - catch ( Exception e ) { - // try to handle general errors more elegantly - LOG.debugf( "Unable to release JDBC result set [%s]", e.getMessage() ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java index 901b22d1f2..2fb6a965d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LogicalConnectionImpl.java @@ -32,14 +32,10 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import org.jboss.logging.Logger; - import org.hibernate.ConnectionReleaseMode; import org.hibernate.HibernateException; import org.hibernate.JDBCException; -import org.hibernate.engine.jdbc.internal.proxy.ProxyBuilder; import org.hibernate.engine.jdbc.spi.ConnectionObserver; -import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.engine.jdbc.spi.NonDurableConnectionObserver; @@ -47,6 +43,7 @@ import org.hibernate.engine.transaction.spi.TransactionContext; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.jboss.logging.Logger; /** * Standard Hibernate {@link org.hibernate.engine.jdbc.spi.LogicalConnection} implementation @@ -54,22 +51,19 @@ import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; * IMPL NOTE : Custom serialization handling! * * @author Steve Ebersole + * @author Brett Meyer */ public class LogicalConnectionImpl implements LogicalConnectionImplementor { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, LogicalConnectionImpl.class.getName() ); private transient Connection physicalConnection; - private transient Connection shareableConnectionProxy; private final transient ConnectionReleaseMode connectionReleaseMode; private final transient JdbcServices jdbcServices; private final transient JdbcConnectionAccess jdbcConnectionAccess; - private final transient JdbcResourceRegistry jdbcResourceRegistry; private final transient List observers; - private boolean releasesEnabled = true; - private final boolean isUserSuppliedConnection; private boolean isClosed; @@ -102,7 +96,6 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { ); this.jdbcServices = jdbcServices; this.jdbcConnectionAccess = jdbcConnectionAccess; - this.jdbcResourceRegistry = new JdbcResourceRegistryImpl( getJdbcServices().getSqlExceptionHelper() ); this.observers = observers; this.isUserSuppliedConnection = isUserSuppliedConnection; @@ -131,11 +124,6 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { return jdbcServices; } - @Override - public JdbcResourceRegistry getResourceRegistry() { - return jdbcResourceRegistry; - } - @Override public void addObserver(ConnectionObserver observer) { observers.add( observer ); @@ -171,30 +159,11 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { return physicalConnection; } - @Override - public Connection getShareableConnectionProxy() { - if ( shareableConnectionProxy == null ) { - shareableConnectionProxy = buildConnectionProxy(); - } - return shareableConnectionProxy; - } - - private Connection buildConnectionProxy() { - return ProxyBuilder.buildConnection( this ); - } - - @Override - public Connection getDistinctConnectionProxy() { - return buildConnectionProxy(); - } - @Override public Connection close() { LOG.trace( "Closing logical connection" ); Connection c = isUserSuppliedConnection ? physicalConnection : null; try { - releaseProxies(); - jdbcResourceRegistry.close(); if ( !isUserSuppliedConnection && physicalConnection != null ) { releaseConnection(); } @@ -212,67 +181,15 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { } } - private void releaseProxies() { - if ( shareableConnectionProxy != null ) { - try { - shareableConnectionProxy.close(); - } - catch (SQLException e) { - LOG.debug( "Error releasing shared connection proxy", e ); - } - } - shareableConnectionProxy = null; - } - @Override public ConnectionReleaseMode getConnectionReleaseMode() { return connectionReleaseMode; } - @Override - public void afterStatementExecution() { - LOG.tracev( "Starting after statement execution processing [{0}]", connectionReleaseMode ); - if ( connectionReleaseMode == ConnectionReleaseMode.AFTER_STATEMENT ) { - if ( ! releasesEnabled ) { - LOG.debug( "Skipping aggressive release due to manual disabling" ); - return; - } - if ( jdbcResourceRegistry.hasRegisteredResources() ) { - LOG.debug( "Skipping aggressive release due to registered resources" ); - return; - } - releaseConnection(); - } - } - - @Override - public void afterTransaction() { - if ( connectionReleaseMode == ConnectionReleaseMode.AFTER_STATEMENT || - connectionReleaseMode == ConnectionReleaseMode.AFTER_TRANSACTION ) { - if ( jdbcResourceRegistry.hasRegisteredResources() ) { - LOG.forcingContainerResourceCleanup(); - jdbcResourceRegistry.releaseResources(); - } - aggressiveRelease(); - } - } - - @Override - public void disableReleases() { - LOG.trace( "Disabling releases" ); - releasesEnabled = false; - } - - @Override - public void enableReleases() { - LOG.trace( "(Re)enabling releases" ); - releasesEnabled = true; - afterStatementExecution(); - } - /** * Force aggressive release of the underlying connection. */ + @Override public void aggressiveRelease() { if ( isUserSuppliedConnection ) { LOG.debug( "Cannot aggressively release user-supplied connection; skipping" ); @@ -310,7 +227,8 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { * * @throws JDBCException Indicates problem closing a connection */ - private void releaseConnection() throws JDBCException { + @Override + public void releaseConnection() throws JDBCException { LOG.debug( "Releasing JDBC connection" ); if ( physicalConnection == null ) { return; @@ -350,9 +268,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { if ( isClosed ) { throw new IllegalStateException( "cannot manually disconnect because logical connection is already closed" ); } - releaseProxies(); Connection c = physicalConnection; - jdbcResourceRegistry.releaseResources(); releaseConnection(); return c; } @@ -395,6 +311,11 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { throw jdbcServices.getSqlExceptionHelper().convert( e, "could not inspect JDBC autocommit mode" ); } } + + @Override + public boolean isUserSuppliedConnection() { + return isUserSuppliedConnection; + } @Override public void notifyObserversStatementPrepared() { @@ -403,13 +324,6 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor { } } - @Override - public boolean isReadyForSerialization() { - return isUserSuppliedConnection - ? ! isPhysicallyConnected() - : ! getResourceRegistry().hasRegisteredResources(); - } - public void serialize(ObjectOutputStream oos) throws IOException { oos.writeBoolean( isUserSuppliedConnection ); oos.writeBoolean( isClosed ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java new file mode 100644 index 0000000000..914137e610 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java @@ -0,0 +1,164 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.engine.jdbc.internal; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.jdbc.spi.ResultSetReturn; +import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; + +/** + * @author Brett Meyer + */ +public class ResultSetReturnImpl implements ResultSetReturn { + + private final JdbcCoordinator jdbcCoordinator; + + public ResultSetReturnImpl(JdbcCoordinator jdbcCoordinator) { + this.jdbcCoordinator = jdbcCoordinator; + } + + @Override + public ResultSet extract(PreparedStatement statement) { + // sql logged by StatementPreparerImpl + if ( statement instanceof CallableStatement ) { + // We actually need to extract from Callable statement. Although + // this seems needless, Oracle can return an + // OracleCallableStatementWrapper that finds its way to this method, + // rather than extract(CallableStatement). See HHH-8022. + CallableStatement callableStatement = (CallableStatement) statement; + return extract( callableStatement ); + } + try { + ResultSet rs = statement.executeQuery(); + postExtract( rs ); + return rs; + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not extract ResultSet" ); + } + } + + @Override + public ResultSet extract(CallableStatement statement) { + try { + // sql logged by StatementPreparerImpl + ResultSet rs = jdbcCoordinator.getLogicalConnection().getJdbcServices() + .getDialect().getResultSet( statement ); + postExtract( rs ); + return rs; + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not extract ResultSet" ); + } + } + + @Override + public ResultSet extract(Statement statement, String sql) { + jdbcCoordinator.getLogicalConnection().getJdbcServices() + .getSqlStatementLogger().logStatement( sql ); + try { + ResultSet rs = statement.executeQuery( sql ); + postExtract( rs ); + return rs; + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not extract ResultSet" ); + } + } + + @Override + public ResultSet execute(PreparedStatement statement) { + // sql logged by StatementPreparerImpl + try { + if ( !statement.execute() ) { + while ( !statement.getMoreResults() && statement.getUpdateCount() != -1 ) { + // do nothing until we hit the resultset + } + } + ResultSet rs = statement.getResultSet(); + postExtract( rs ); + return rs; + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not execute statement" ); + } + } + + @Override + public ResultSet execute(Statement statement, String sql) { + jdbcCoordinator.getLogicalConnection().getJdbcServices() + .getSqlStatementLogger().logStatement( sql ); + try { + if ( !statement.execute( sql ) ) { + while ( !statement.getMoreResults() && statement.getUpdateCount() != -1 ) { + // do nothing until we hit the resultset + } + } + ResultSet rs = statement.getResultSet(); + postExtract( rs ); + return rs; + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not execute statement" ); + } + } + + @Override + public int executeUpdate( PreparedStatement statement ) { + try { + return statement.executeUpdate(); + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not execute statement" ); + } + } + + @Override + public int executeUpdate( Statement statement, String sql ) { + jdbcCoordinator.getLogicalConnection().getJdbcServices() + .getSqlStatementLogger().logStatement( sql ); + try { + return statement.executeUpdate( sql ); + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not execute statement" ); + } + } + + private final SqlExceptionHelper sqlExceptionHelper() { + return jdbcCoordinator.getTransactionCoordinator() + .getTransactionContext() + .getTransactionEnvironment() + .getJdbcServices() + .getSqlExceptionHelper(); + } + + private void postExtract(ResultSet rs) { + if ( rs != null ) jdbcCoordinator.register( rs ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java index d8fe244dcb..ef579209c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java @@ -27,6 +27,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import org.hibernate.AssertionFailure; import org.hibernate.ScrollMode; @@ -38,6 +39,7 @@ import org.hibernate.engine.jdbc.spi.StatementPreparer; /** * @author Steve Ebersole * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + * @author Brett Meyer */ class StatementPreparerImpl implements StatementPreparer { private JdbcCoordinatorImpl jdbcCoordinator; @@ -50,8 +52,8 @@ class StatementPreparerImpl implements StatementPreparer { return jdbcCoordinator.sessionFactory().getSettings(); } - protected final Connection connectionProxy() { - return logicalConnection().getShareableConnectionProxy(); + protected final Connection connection() { + return logicalConnection().getConnection(); } protected final LogicalConnectionImplementor logicalConnection() { @@ -65,6 +67,18 @@ class StatementPreparerImpl implements StatementPreparer { .getJdbcServices() .getSqlExceptionHelper(); } + + @Override + public Statement createStatement() { + try { + Statement statement = connection().createStatement(); + jdbcCoordinator.register( statement ); + return statement; + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "could not create statement" ); + } + } @Override public PreparedStatement prepareStatement(String sql) { @@ -82,8 +96,8 @@ class StatementPreparerImpl implements StatementPreparer { @Override protected PreparedStatement doPrepare() throws SQLException { return isCallable - ? connectionProxy().prepareCall( sql ) - : connectionProxy().prepareStatement( sql ); + ? connection().prepareCall( sql ) + : connection().prepareStatement( sql ); } }; } @@ -102,7 +116,7 @@ class StatementPreparerImpl implements StatementPreparer { jdbcCoordinator.executeBatch(); return new StatementPreparationTemplate( sql ) { public PreparedStatement doPrepare() throws SQLException { - return connectionProxy().prepareStatement( sql, autoGeneratedKeys ); + return connection().prepareStatement( sql, autoGeneratedKeys ); } }.prepareStatement(); } @@ -113,7 +127,7 @@ class StatementPreparerImpl implements StatementPreparer { jdbcCoordinator.executeBatch(); return new StatementPreparationTemplate( sql ) { public PreparedStatement doPrepare() throws SQLException { - return connectionProxy().prepareStatement( sql, columnNames ); + return connection().prepareStatement( sql, columnNames ); } }.prepareStatement(); } @@ -130,26 +144,26 @@ class StatementPreparerImpl implements StatementPreparer { PreparedStatement ps = new QueryStatementPreparationTemplate( sql ) { public PreparedStatement doPrepare() throws SQLException { return isCallable - ? connectionProxy().prepareCall( + ? connection().prepareCall( sql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY ) - : connectionProxy().prepareStatement( + : connection().prepareStatement( sql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY ); } }.prepareStatement(); - logicalConnection().getResourceRegistry().registerLastQuery( ps ); + jdbcCoordinator.registerLastQuery( ps ); return ps; } else { PreparedStatement ps = new QueryStatementPreparationTemplate( sql ) { public PreparedStatement doPrepare() throws SQLException { return isCallable - ? connectionProxy().prepareCall( sql ) - : connectionProxy().prepareStatement( sql ); + ? connection().prepareCall( sql ) + : connection().prepareStatement( sql ); } }.prepareStatement(); - logicalConnection().getResourceRegistry().registerLastQuery( ps ); + jdbcCoordinator.registerLastQuery( ps ); return ps; } } @@ -163,6 +177,8 @@ class StatementPreparerImpl implements StatementPreparer { public PreparedStatement prepareStatement() { try { + jdbcCoordinator.getLogicalConnection().getJdbcServices().getSqlStatementLogger().logStatement( sql ); + PreparedStatement preparedStatement = doPrepare(); setStatementTimeout( preparedStatement ); postProcess( preparedStatement ); @@ -176,6 +192,8 @@ class StatementPreparerImpl implements StatementPreparer { protected abstract PreparedStatement doPrepare() throws SQLException; public void postProcess(PreparedStatement preparedStatement) throws SQLException { + jdbcCoordinator.register( preparedStatement ); + logicalConnection().notifyObserversStatementPrepared(); } private void setStatementTimeout(PreparedStatement preparedStatement) throws SQLException { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractProxyHandler.java deleted file mode 100644 index 83ba956795..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractProxyHandler.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; - -import org.hibernate.HibernateException; - -/** - * Basic support for building proxy handlers. - * - * @author Steve Ebersole - */ -public abstract class AbstractProxyHandler implements InvocationHandler { - private boolean valid = true; - private final int hashCode; - - public AbstractProxyHandler(int hashCode) { - this.hashCode = hashCode; - } - - protected abstract Object continueInvocation(Object proxy, Method method, Object[] args) throws Throwable; - - public String toString() { - return super.toString() + "[valid=" + valid + "]"; - } - - public final int hashCode() { - return hashCode; - } - - protected final boolean isValid() { - return valid; - } - - protected final void invalidate() { - valid = false; - } - - protected final void errorIfInvalid() { - if ( !isValid() ) { - throw new HibernateException( "proxy handle is no longer valid" ); - } - } - - public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String methodName = method.getName(); - - // basic Object methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - if ( "toString".equals( methodName ) ) { - return this.toString(); - } - if ( "hashCode".equals( methodName ) ) { - return this.hashCode(); - } - if ( "equals".equals( methodName ) ) { - return this.equals( args[0] ); - } - - return continueInvocation( proxy, method, args ); - } - -} \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractResultSetProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractResultSetProxyHandler.java deleted file mode 100644 index 91f2c250fd..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractResultSetProxyHandler.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.jboss.logging.Logger; - -import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.internal.CoreMessageLogger; - -/** - * Basic support for building {@link ResultSet}-based proxy handlers - * - * @author Steve Ebersole - */ -public abstract class AbstractResultSetProxyHandler extends AbstractProxyHandler { - - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, - AbstractResultSetProxyHandler.class.getName()); - - private ResultSet resultSet; - - public AbstractResultSetProxyHandler(ResultSet resultSet) { - super( resultSet.hashCode() ); - this.resultSet = resultSet; - } - - protected abstract JdbcServices getJdbcServices(); - - protected abstract JdbcResourceRegistry getResourceRegistry(); - - protected abstract Statement getExposableStatement(); - - protected final ResultSet getResultSet() { - errorIfInvalid(); - return resultSet; - } - - protected final ResultSet getResultSetWithoutChecks() { - return resultSet; - } - - @Override - protected Object continueInvocation(Object proxy, Method method, Object[] args) throws Throwable { - final String methodName = method.getName(); - LOG.tracev( "Handling invocation of ResultSet method [{0}]", methodName ); - - // other methods allowed while invalid ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - if ( "close".equals( methodName ) ) { - explicitClose( ( ResultSet ) proxy ); - return null; - } - if ( "invalidate".equals( methodName ) ) { - invalidateHandle(); - return null; - } - - errorIfInvalid(); - - // handle the JDBC 4 Wrapper#isWrapperFor and Wrapper#unwrap calls - // these cause problems to the whole proxy scheme though as we need to return the raw objects - if ( "isWrapperFor".equals( methodName ) && args.length == 1 ) { - return method.invoke( getResultSetWithoutChecks(), args ); - } - if ( "unwrap".equals( methodName ) && args.length == 1 ) { - return method.invoke( getResultSetWithoutChecks(), args ); - } - - if ( "getWrappedObject".equals( methodName ) ) { - return getResultSetWithoutChecks(); - } - - if ( "getStatement".equals( methodName ) ) { - return getExposableStatement(); - } - - try { - return method.invoke( resultSet, args ); - } - catch ( InvocationTargetException e ) { - Throwable realException = e.getTargetException(); - if (SQLException.class.isInstance(realException)) throw getJdbcServices().getSqlExceptionHelper().convert((SQLException)realException, - realException.getMessage()); - throw realException; - } - } - - private void explicitClose(ResultSet proxy) { - if ( isValid() ) { - getResourceRegistry().release( proxy ); - } - } - - protected void invalidateHandle() { - resultSet = null; - invalidate(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractStatementProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractStatementProxyHandler.java deleted file mode 100644 index cfdd80523a..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/AbstractStatementProxyHandler.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.jboss.logging.Logger; - -import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; -import org.hibernate.internal.CoreMessageLogger; - -/** - * Basic support for building {@link Statement}-based proxy handlers - * - * @author Steve Ebersole - */ -public abstract class AbstractStatementProxyHandler extends AbstractProxyHandler { - - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, - AbstractStatementProxyHandler.class.getName()); - - private ConnectionProxyHandler connectionProxyHandler; - private Connection connectionProxy; - private Statement statement; - - protected AbstractStatementProxyHandler( - Statement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - super( statement.hashCode() ); - this.statement = statement; - this.connectionProxyHandler = connectionProxyHandler; - this.connectionProxy = connectionProxy; - } - - protected ConnectionProxyHandler getConnectionProxy() { - errorIfInvalid(); - return connectionProxyHandler; - } - - protected JdbcServices getJdbcServices() { - return getConnectionProxy().getJdbcServices(); - } - - protected JdbcResourceRegistry getResourceRegistry() { - return getConnectionProxy().getResourceRegistry(); - } - - protected Statement getStatement() { - errorIfInvalid(); - return statement; - } - - protected Statement getStatementWithoutChecks() { - return statement; - } - - @Override - protected Object continueInvocation(Object proxy, Method method, Object[] args) throws Throwable { - final String methodName = method.getName(); - LOG.tracev( "Handling invocation of statement method [{0}]", methodName ); - - // other methods allowed while invalid ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - if ( "close".equals( methodName ) ) { - explicitClose( ( Statement ) proxy ); - return null; - } - if ( "invalidate".equals( methodName ) ) { - invalidateHandle(); - return null; - } - - errorIfInvalid(); - - // handle the JDBC 4 Wrapper#isWrapperFor and Wrapper#unwrap calls - // these cause problems to the whole proxy scheme though as we need to return the raw objects - if ( "isWrapperFor".equals( methodName ) && args.length == 1 ) { - return method.invoke( getStatementWithoutChecks(), args ); - } - if ( "unwrap".equals( methodName ) && args.length == 1 ) { - return method.invoke( getStatementWithoutChecks(), args ); - } - - if ( "getWrappedObject".equals( methodName ) ) { - return getStatementWithoutChecks(); - } - - if ( "getConnection".equals( methodName ) ) { - return connectionProxy; - } - - beginningInvocationHandling( method, args ); - - try { - Object result = method.invoke( statement, args ); - result = wrapIfNecessary( result, proxy, method ); - return result; - } - catch ( InvocationTargetException e ) { - Throwable realException = e.getTargetException(); - if ( SQLException.class.isInstance( realException ) ) { - throw connectionProxyHandler.getJdbcServices().getSqlExceptionHelper() - .convert( ( SQLException ) realException, realException.getMessage() ); - } - else { - throw realException; - } - } - } - - private Object wrapIfNecessary(Object result, Object proxy, Method method) { - if ( !( ResultSet.class.isAssignableFrom( method.getReturnType() ) ) ) { - return result; - } - - final ResultSet wrapper; - if ( "getGeneratedKeys".equals( method.getName() ) ) { - wrapper = ProxyBuilder.buildImplicitResultSet( ( ResultSet ) result, connectionProxyHandler, connectionProxy, ( Statement ) proxy ); - } - else { - wrapper = ProxyBuilder.buildResultSet( ( ResultSet ) result, this, ( Statement ) proxy ); - } - getResourceRegistry().register( wrapper ); - return wrapper; - } - - protected void beginningInvocationHandling(Method method, Object[] args) { - } - - private void explicitClose(Statement proxy) { - if ( isValid() ) { - LogicalConnectionImplementor lc = getConnectionProxy().getLogicalConnection(); - getResourceRegistry().release( proxy ); - lc.afterStatementExecution(); - } - } - - private void invalidateHandle() { - connectionProxyHandler = null; - statement = null; - invalidate(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/CallableStatementProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/CallableStatementProxyHandler.java deleted file mode 100644 index 0460e2f160..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/CallableStatementProxyHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.Method; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; - -/** - * Invocation handler for {@link java.sql.CallableStatement} proxies - * - * @author Gail Badner - */ -public class CallableStatementProxyHandler extends PreparedStatementProxyHandler { - - protected CallableStatementProxyHandler( - String sql, - Statement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - super( sql, statement, connectionProxyHandler, connectionProxy ); - } - - @Override - protected Object continueInvocation(Object proxy, Method method, Object[] args) throws Throwable { - if ( ! "executeQuery".equals( method.getName() ) ) { - return super.continueInvocation( proxy, method, args ); // EARLY RETURN! - } - errorIfInvalid(); - return executeQuery(); - } - - private Object executeQuery() throws SQLException { - return getConnectionProxy().getJdbcServices().getDialect().getResultSet( ( CallableStatement ) getStatementWithoutChecks() ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ConnectionProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ConnectionProxyHandler.java deleted file mode 100644 index 4529e45e29..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ConnectionProxyHandler.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; - -import org.jboss.logging.Logger; - -import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; -import org.hibernate.engine.jdbc.spi.NonDurableConnectionObserver; -import org.hibernate.internal.CoreMessageLogger; - -/** - * The {@link InvocationHandler} for intercepting messages to {@link java.sql.Connection} proxies. - * - * @author Steve Ebersole - */ -public class ConnectionProxyHandler - extends AbstractProxyHandler - implements NonDurableConnectionObserver { - - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, - ConnectionProxyHandler.class.getName()); - - private LogicalConnectionImplementor logicalConnection; - - public ConnectionProxyHandler(LogicalConnectionImplementor logicalConnection) { - super( logicalConnection.hashCode() ); - this.logicalConnection = logicalConnection; - this.logicalConnection.addObserver( this ); - } - - /** - * Access to our logical connection. - * - * @return the logical connection - */ - protected LogicalConnectionImplementor getLogicalConnection() { - errorIfInvalid(); - return logicalConnection; - } - - /** - * Get reference to physical connection. - *

- * NOTE : be sure this handler is still valid before calling! - * - * @return The physical connection - */ - private Connection extractPhysicalConnection() { - return logicalConnection.getConnection(); - } - - /** - * Provide access to JDBCServices. - *

- * NOTE : package-protected - * - * @return JDBCServices - */ - JdbcServices getJdbcServices() { - return logicalConnection.getJdbcServices(); - } - - /** - * Provide access to JDBCContainer. - *

- * NOTE : package-protected - * - * @return JDBCContainer - */ - JdbcResourceRegistry getResourceRegistry() { - return logicalConnection.getResourceRegistry(); - } - - @Override - protected Object continueInvocation(Object proxy, Method method, Object[] args) throws Throwable { - final String methodName = method.getName(); - LOG.tracev( "Handling invocation of connection method [{0}]", methodName ); - - // other methods allowed while invalid ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - if ( "close".equals( methodName ) ) { - explicitClose(); - return null; - } - - if ( "isClosed".equals( methodName ) ) { - return ! isValid(); - } - - errorIfInvalid(); - - // handle the JDBC 4 Wrapper#isWrapperFor and Wrapper#unwrap calls - // these cause problems to the whole proxy scheme though as we need to return the raw objects - if ( "isWrapperFor".equals( methodName ) && args.length == 1 ) { - return method.invoke( extractPhysicalConnection(), args ); - } - if ( "unwrap".equals( methodName ) && args.length == 1 ) { - return method.invoke( extractPhysicalConnection(), args ); - } - - if ( "getWrappedObject".equals( methodName ) ) { - return extractPhysicalConnection(); - } - - try { - Object result = method.invoke( extractPhysicalConnection(), args ); - result = postProcess( result, proxy, method, args ); - - return result; - } - catch( InvocationTargetException e ) { - Throwable realException = e.getTargetException(); - if ( SQLException.class.isInstance( realException ) ) { - throw logicalConnection.getJdbcServices().getSqlExceptionHelper() - .convert( ( SQLException ) realException, realException.getMessage() ); - } - else { - throw realException; - } - } - } - - private Object postProcess(Object result, Object proxy, Method method, Object[] args) throws SQLException { - String methodName = method.getName(); - Object wrapped = result; - if ( "createStatement".equals( methodName ) ) { - wrapped = ProxyBuilder.buildStatement( - (Statement) result, - this, - ( Connection ) proxy - ); - postProcessStatement( ( Statement ) wrapped ); - } - else if ( "prepareStatement".equals( methodName ) ) { - wrapped = ProxyBuilder.buildPreparedStatement( - ( String ) args[0], - (PreparedStatement) result, - this, - ( Connection ) proxy - ); - postProcessPreparedStatement( ( Statement ) wrapped ); - } - else if ( "prepareCall".equals( methodName ) ) { - wrapped = ProxyBuilder.buildCallableStatement( - ( String ) args[0], - (CallableStatement) result, - this, - ( Connection ) proxy - ); - postProcessPreparedStatement( ( Statement ) wrapped ); - } - else if ( "getMetaData".equals( methodName ) ) { - wrapped = ProxyBuilder.buildDatabaseMetaData( (DatabaseMetaData) result, this, ( Connection ) proxy ); - } - return wrapped; - } - - private void postProcessStatement(Statement statement) throws SQLException { - getResourceRegistry().register( statement ); - } - - private void postProcessPreparedStatement(Statement statement) throws SQLException { - logicalConnection.notifyObserversStatementPrepared(); - postProcessStatement( statement ); - } - - private void explicitClose() { - if ( isValid() ) { - invalidateHandle(); - } - } - - private void invalidateHandle() { - LOG.trace( "Invalidating connection handle" ); - logicalConnection = null; - invalidate(); - } - - // ConnectionObserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - @Override - public void physicalConnectionObtained(Connection connection) { - } - - @Override - public void physicalConnectionReleased() { - LOG.logicalConnectionReleasingPhysicalConnection(); - } - - @Override - public void logicalConnectionClosed() { - LOG.logicalConnectionClosed(); - invalidateHandle(); - } - - @Override - public void statementPrepared() { - // N/A - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/DatabaseMetaDataProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/DatabaseMetaDataProxyHandler.java deleted file mode 100644 index db74b8e243..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/DatabaseMetaDataProxyHandler.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * The InvocationHandler for intercepting messages to {@link java.sql.DatabaseMetaData} proxies. - *

- * Mainly we need to intercept the methods defined on {@link java.sql.DatabaseMetaData} which expose - * {@link java.sql.ResultSet} instances, which in turn expose {@link java.sql.Statement} - * instances, which in turn... - * - * @author Steve Ebersole - */ -public class DatabaseMetaDataProxyHandler extends AbstractProxyHandler { - private ConnectionProxyHandler connectionProxyHandler; - private Connection connectionProxy; - private DatabaseMetaData databaseMetaData; - - public DatabaseMetaDataProxyHandler(DatabaseMetaData databaseMetaData, ConnectionProxyHandler connectionProxyHandler, Connection connectionProxy) { - super( databaseMetaData.hashCode() ); - this.connectionProxyHandler = connectionProxyHandler; - this.connectionProxy = connectionProxy; - this.databaseMetaData = databaseMetaData; - } - - protected Object continueInvocation(Object proxy, Method method, Object[] args) throws Throwable { - // handle the JDBC 4 Wrapper#isWrapperFor and Wrapper#unwrap calls - // these cause problems to the whole proxy scheme though as we need to return the raw objects - if ( "isWrapperFor".equals( method.getName() ) && args.length == 1 ) { - return method.invoke( databaseMetaData, args ); - } - if ( "unwrap".equals( method.getName() ) && args.length == 1 ) { - return method.invoke( databaseMetaData, args ); - } - - try { - boolean exposingResultSet = doesMethodExposeResultSet( method ); - - Object result = method.invoke( databaseMetaData, args ); - - if ( exposingResultSet ) { - result = ProxyBuilder.buildImplicitResultSet( (ResultSet) result, connectionProxyHandler, connectionProxy ); - connectionProxyHandler.getResourceRegistry().register( ( ResultSet ) result ); - } - - return result; - } - catch ( InvocationTargetException e ) { - Throwable realException = e.getTargetException(); - if ( SQLException.class.isInstance( realException ) ) { - throw connectionProxyHandler.getJdbcServices().getSqlExceptionHelper() - .convert( ( SQLException ) realException, realException.getMessage() ); - } - else { - throw realException; - } - } - } - - protected boolean doesMethodExposeResultSet(Method method) { - return ResultSet.class.isAssignableFrom( method.getReturnType() ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ImplicitResultSetProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ImplicitResultSetProxyHandler.java deleted file mode 100644 index 24de36c88c..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ImplicitResultSetProxyHandler.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; -import org.hibernate.engine.jdbc.spi.JdbcServices; - -/** - * Invocation handler for {@link java.sql.ResultSet} proxies obtained from other JDBC object proxies - * - * @author Steve Ebersole - */ -public class ImplicitResultSetProxyHandler extends AbstractResultSetProxyHandler { - private ConnectionProxyHandler connectionProxyHandler; - private Connection connectionProxy; - private Statement sourceStatement; - - public ImplicitResultSetProxyHandler(ResultSet resultSet, ConnectionProxyHandler connectionProxyHandler, Connection connectionProxy) { - super( resultSet ); - this.connectionProxyHandler = connectionProxyHandler; - this.connectionProxy = connectionProxy; - } - - public ImplicitResultSetProxyHandler(ResultSet resultSet, ConnectionProxyHandler connectionProxyHandler, Connection connectionProxy, Statement sourceStatement) { - super( resultSet ); - this.connectionProxyHandler = connectionProxyHandler; - this.connectionProxy = connectionProxy; - this.sourceStatement = sourceStatement; - } - - @Override - protected JdbcServices getJdbcServices() { - return connectionProxyHandler.getJdbcServices(); - } - - @Override - protected JdbcResourceRegistry getResourceRegistry() { - return connectionProxyHandler.getResourceRegistry(); - } - - @Override - protected Statement getExposableStatement() { - if ( sourceStatement == null ) { - try { - Statement stmnt = getResultSet().getStatement(); - if ( stmnt == null ) { - return null; - } - sourceStatement = ProxyBuilder.buildImplicitStatement( stmnt, connectionProxyHandler, connectionProxy ); - } - catch ( SQLException e ) { - throw getJdbcServices().getSqlExceptionHelper().convert( e, e.getMessage() ); - } - } - return sourceStatement; - } - - protected void invalidateHandle() { - sourceStatement = null; - super.invalidateHandle(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/PreparedStatementProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/PreparedStatementProxyHandler.java deleted file mode 100644 index 872074ec51..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/PreparedStatementProxyHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.Method; -import java.sql.Connection; -import java.sql.Statement; -import java.util.Arrays; - -import org.jboss.logging.Logger; - -import org.hibernate.internal.CoreMessageLogger; - -/** - * Invocation handler for {@link java.sql.PreparedStatement} proxies - * - * @author Steve Ebersole - */ -public class PreparedStatementProxyHandler extends AbstractStatementProxyHandler { - - private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, PreparedStatementProxyHandler.class.getName() ); - - private final String sql; - - protected PreparedStatementProxyHandler( - String sql, - Statement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - super( statement, connectionProxyHandler, connectionProxy ); - connectionProxyHandler.getJdbcServices().getSqlStatementLogger().logStatement( sql ); - this.sql = sql; - } - - @Override - protected void beginningInvocationHandling(Method method, Object[] args) { - if ( isExecution( method ) ) { - logExecution(); - } - else { - journalPossibleParameterBind( method, args ); - } - } - - private void journalPossibleParameterBind(Method method, Object[] args) { - String methodName = method.getName(); - // todo : is this enough??? - if ( methodName.startsWith( "set" ) && args != null && args.length >= 2 ) { - journalParameterBind( method, args ); - } - } - - private void journalParameterBind(Method method, Object[] args) { - if ( LOG.isTraceEnabled() ) { - LOG.tracev( "Binding via {0}: {1}", method.getName(), Arrays.asList( args ) ); - } - } - - private boolean isExecution(Method method) { - return false; - } - - private void logExecution() { - } -} \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ProxyBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ProxyBuilder.java deleted file mode 100644 index e297a9e10c..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ProxyBuilder.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Proxy; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; - -import org.hibernate.engine.jdbc.spi.InvalidatableWrapper; -import org.hibernate.engine.jdbc.spi.JdbcWrapper; -import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; -import org.hibernate.internal.util.ValueHolder; - -/** - * Centralized builder for proxy instances - * - * @author Steve Ebersole - */ -public class ProxyBuilder { - - // Connection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public static final Class[] CONNECTION_PROXY_INTERFACES = new Class[] { - Connection.class, - JdbcWrapper.class - }; - - private static final ValueHolder> connectionProxyConstructorValue = new ValueHolder>( - new ValueHolder.DeferredInitializer>() { - @Override - public Constructor initialize() { - try { - return locateConnectionProxyClass().getConstructor( InvocationHandler.class ); - } - catch (NoSuchMethodException e) { - throw new JdbcProxyException( "Could not find proxy constructor in JDK generated Connection proxy class", e ); - } - } - - @SuppressWarnings("unchecked") - private Class locateConnectionProxyClass() { - return (Class) Proxy.getProxyClass( - JdbcWrapper.class.getClassLoader(), - CONNECTION_PROXY_INTERFACES - ); - } - } - ); - - public static Connection buildConnection(LogicalConnectionImplementor logicalConnection) { - final ConnectionProxyHandler proxyHandler = new ConnectionProxyHandler( logicalConnection ); - try { - return connectionProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC Connection proxy", e ); - } - } - - - // Statement ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public static final Class[] STMNT_PROXY_INTERFACES = new Class[] { - Statement.class, - JdbcWrapper.class, - InvalidatableWrapper.class - }; - - private static final ValueHolder> statementProxyConstructorValue = new ValueHolder>( - new ValueHolder.DeferredInitializer>() { - @Override - public Constructor initialize() { - try { - return locateStatementProxyClass().getConstructor( InvocationHandler.class ); - } - catch (NoSuchMethodException e) { - throw new JdbcProxyException( "Could not find proxy constructor in JDK generated Statement proxy class", e ); - } - } - - @SuppressWarnings("unchecked") - private Class locateStatementProxyClass() { - return (Class) Proxy.getProxyClass( - JdbcWrapper.class.getClassLoader(), - STMNT_PROXY_INTERFACES - ); - } - } - ); - - public static Statement buildStatement( - Statement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - final BasicStatementProxyHandler proxyHandler = new BasicStatementProxyHandler( - statement, - connectionProxyHandler, - connectionProxy - ); - try { - return statementProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC Statement proxy", e ); - } - } - - public static Statement buildImplicitStatement( - Statement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - if ( statement == null ) { - return null; - } - final ImplicitStatementProxyHandler proxyHandler = new ImplicitStatementProxyHandler( statement, connectionProxyHandler, connectionProxy ); - try { - return statementProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC Statement proxy", e ); - } - } - - - // PreparedStatement ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public static final Class[] PREPARED_STMNT_PROXY_INTERFACES = new Class[] { - PreparedStatement.class, - JdbcWrapper.class, - InvalidatableWrapper.class - }; - - private static final ValueHolder> preparedStatementProxyConstructorValue = new ValueHolder>( - new ValueHolder.DeferredInitializer>() { - @Override - public Constructor initialize() { - try { - return locatePreparedStatementProxyClass().getConstructor( InvocationHandler.class ); - } - catch (NoSuchMethodException e) { - throw new JdbcProxyException( "Could not find proxy constructor in JDK generated Statement proxy class", e ); - } - } - - @SuppressWarnings("unchecked") - private Class locatePreparedStatementProxyClass() { - return (Class) Proxy.getProxyClass( - JdbcWrapper.class.getClassLoader(), - PREPARED_STMNT_PROXY_INTERFACES - ); - } - } - ); - - public static PreparedStatement buildPreparedStatement( - String sql, - Statement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - final PreparedStatementProxyHandler proxyHandler = new PreparedStatementProxyHandler( - sql, - statement, - connectionProxyHandler, - connectionProxy - ); - try { - return preparedStatementProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC PreparedStatement proxy", e ); - } - } - - - // CallableStatement ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public static final Class[] CALLABLE_STMNT_PROXY_INTERFACES = new Class[] { - CallableStatement.class, - JdbcWrapper.class, - InvalidatableWrapper.class - }; - - private static final ValueHolder> callableStatementProxyConstructorValue = new ValueHolder>( - new ValueHolder.DeferredInitializer>() { - @Override - public Constructor initialize() { - try { - return locateCallableStatementProxyClass().getConstructor( InvocationHandler.class ); - } - catch (NoSuchMethodException e) { - throw new JdbcProxyException( "Could not find proxy constructor in JDK generated Statement proxy class", e ); - } - } - - @SuppressWarnings("unchecked") - private Class locateCallableStatementProxyClass() { - return (Class) Proxy.getProxyClass( - JdbcWrapper.class.getClassLoader(), - CALLABLE_STMNT_PROXY_INTERFACES - ); - } - } - ); - - public static CallableStatement buildCallableStatement( - String sql, - CallableStatement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - final CallableStatementProxyHandler proxyHandler = new CallableStatementProxyHandler( - sql, - statement, - connectionProxyHandler, - connectionProxy - ); - try { - return callableStatementProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC CallableStatement proxy", e ); - } - } - - - // ResultSet ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public static final Class[] RESULTSET_PROXY_INTERFACES = new Class[] { - ResultSet.class, - JdbcWrapper.class, - InvalidatableWrapper.class - }; - - private static final ValueHolder> resultSetProxyConstructorValue = new ValueHolder>( - new ValueHolder.DeferredInitializer>() { - @Override - public Constructor initialize() { - try { - return locateCallableStatementProxyClass().getConstructor( InvocationHandler.class ); - } - catch (NoSuchMethodException e) { - throw new JdbcProxyException( "Could not find proxy constructor in JDK generated ResultSet proxy class", e ); - } - } - - @SuppressWarnings("unchecked") - private Class locateCallableStatementProxyClass() { - return (Class) Proxy.getProxyClass( - JdbcWrapper.class.getClassLoader(), - RESULTSET_PROXY_INTERFACES - ); - } - } - ); - - public static ResultSet buildResultSet( - ResultSet resultSet, - AbstractStatementProxyHandler statementProxyHandler, - Statement statementProxy) { - final ResultSetProxyHandler proxyHandler = new ResultSetProxyHandler( resultSet, statementProxyHandler, statementProxy ); - try { - return resultSetProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC ResultSet proxy", e ); - } - } - - public static ResultSet buildImplicitResultSet( - ResultSet resultSet, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - final ImplicitResultSetProxyHandler proxyHandler = new ImplicitResultSetProxyHandler( - resultSet, - connectionProxyHandler, - connectionProxy - ); - try { - return resultSetProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC ResultSet proxy", e ); - } - } - - public static ResultSet buildImplicitResultSet( - ResultSet resultSet, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy, - Statement sourceStatement) { - final ImplicitResultSetProxyHandler proxyHandler = new ImplicitResultSetProxyHandler( - resultSet, - connectionProxyHandler, - connectionProxy, - sourceStatement - ); - try { - return resultSetProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC ResultSet proxy", e ); - } - } - - - // DatabaseMetaData ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public static final Class[] METADATA_PROXY_INTERFACES = new Class[] { - DatabaseMetaData.class, - JdbcWrapper.class - }; - - private static final ValueHolder> metadataProxyConstructorValue = new ValueHolder>( - new ValueHolder.DeferredInitializer>() { - @Override - public Constructor initialize() { - try { - return locateDatabaseMetaDataProxyClass().getConstructor( InvocationHandler.class ); - } - catch (NoSuchMethodException e) { - throw new JdbcProxyException( "Could not find proxy constructor in JDK generated DatabaseMetaData proxy class", e ); - } - } - - @SuppressWarnings("unchecked") - private Class locateDatabaseMetaDataProxyClass() { - return (Class) Proxy.getProxyClass( - JdbcWrapper.class.getClassLoader(), - METADATA_PROXY_INTERFACES - ); - } - } - ); - - public static DatabaseMetaData buildDatabaseMetaData( - DatabaseMetaData metaData, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - final DatabaseMetaDataProxyHandler proxyHandler = new DatabaseMetaDataProxyHandler( - metaData, - connectionProxyHandler, - connectionProxy - ); - try { - return metadataProxyConstructorValue.getValue().newInstance( proxyHandler ); - } - catch (Exception e) { - throw new JdbcProxyException( "Could not instantiate JDBC DatabaseMetaData proxy", e ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ResultSetProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ResultSetProxyHandler.java deleted file mode 100644 index 996f855bac..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ResultSetProxyHandler.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.sql.ResultSet; -import java.sql.Statement; - -import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; -import org.hibernate.engine.jdbc.spi.JdbcServices; - -/** - * Invocation handler for {@link java.sql.ResultSet} proxies - * - * @author Steve Ebersole - */ -public class ResultSetProxyHandler extends AbstractResultSetProxyHandler { - private AbstractStatementProxyHandler statementProxyHandler; - private Statement statementProxy; - - public ResultSetProxyHandler( - ResultSet resultSet, - AbstractStatementProxyHandler statementProxyHandler, - Statement statementProxy) { - super( resultSet ); - this.statementProxyHandler = statementProxyHandler; - this.statementProxy = statementProxy; - } - - protected AbstractStatementProxyHandler getStatementProxy() { - return statementProxyHandler; - } - - protected Statement getExposableStatement() { - return statementProxy; - } - - protected JdbcServices getJdbcServices() { - return getStatementProxy().getJdbcServices(); - } - - protected JdbcResourceRegistry getResourceRegistry() { - return getStatementProxy().getResourceRegistry(); - } - - protected void invalidateHandle() { - statementProxyHandler = null; - super.invalidateHandle(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java index 7240f6014a..7c6e4eb9cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java @@ -25,6 +25,8 @@ package org.hibernate.engine.jdbc.spi; import java.io.Serializable; import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.batch.spi.BatchKey; @@ -35,6 +37,7 @@ import org.hibernate.jdbc.WorkExecutorVisitable; * Coordinates JDBC-related activities. * * @author Steve Ebersole + * @author Brett Meyer */ public interface JdbcCoordinator extends Serializable { /** @@ -77,6 +80,13 @@ public interface JdbcCoordinator extends Serializable { */ public StatementPreparer getStatementPreparer(); + /** + * Obtain the resultset extractor associated with this JDBC coordinator. + * + * @return This coordinator's resultset extractor + */ + public ResultSetReturn getResultSetReturn(); + /** * Callback to let us know that a flush is beginning. We use this fact * to temporarily circumvent aggressive connection releasing until after @@ -107,6 +117,13 @@ public interface JdbcCoordinator extends Serializable { */ public void afterTransaction(); + /** + * Used to signify that a statement has completed execution which may + * indicate that this logical connection need to perform an + * aggressive release of its physical connection. + */ + public void afterStatementExecution(); + /** * Perform the requested work handling exceptions, coordinating and handling return processing. * @@ -137,4 +154,56 @@ public interface JdbcCoordinator extends Serializable { * @throws org.hibernate.TransactionException Indicates the time out period has already been exceeded. */ public int determineRemainingTransactionTimeOutPeriod(); + /** + * Register a JDBC statement. + * + * @param statement The statement to register. + */ + public void register(Statement statement); + + /** + * Release a previously registered statement. + * + * @param statement The statement to release. + */ + public void release(Statement statement); + + /** + * Register a JDBC result set. + * + * @param resultSet The result set to register. + */ + public void register(ResultSet resultSet); + + /** + * Release a previously registered result set. + * + * @param resultSet The result set to release. + */ + public void release(ResultSet resultSet); + + /** + * Does this registry currently have any registered resources? + * + * @return True if the registry does have registered resources; false otherwise. + */ + public boolean hasRegisteredResources(); + + /** + * Release all registered resources. + */ + public void releaseResources(); + + public void enableReleases(); + + public void disableReleases(); + + /** + * Register a query statement as being able to be cancelled. + * + * @param statement The cancel-able query statement. + */ + public void registerLastQuery(Statement statement); + + public boolean isReadyForSerialization(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcResourceRegistry.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcResourceRegistry.java deleted file mode 100644 index 7596322428..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcResourceRegistry.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine.jdbc.spi; - -import java.io.Serializable; -import java.sql.ResultSet; -import java.sql.Statement; - -/** - * Defines a registry of JDBC resources related to a particular unit of work. The main function of a - * JdbcResourceRegistry is to make sure resources get cleaned up. This is accomplished by registering all - * JDBC-related resources via the {@link #register(java.sql.Statement)} and {@link #register(java.sql.ResultSet)} - * methods. When done with these resources, they should be released by the corollary - * {@link #release(java.sql.Statement)} and {@link #release(java.sql.ResultSet)} methods. Any un-released resources - * will be released automatically when this registry is closed via {@link #close()}. Additionally, - * all registered resources can be released at any time using {@link #releaseResources()}. - *

- * Additionally, a query can be registered as being able to be cancelled via the {@link #registerLastQuery} - * method. Such statements can then be cancelled by calling {@link #cancelLastQuery()} - * - * @author Steve Ebersole - */ -public interface JdbcResourceRegistry extends Serializable { - /** - * Register a JDBC statement. - * - * @param statement The statement to register. - */ - public void register(Statement statement); - - /** - * Release a previously registered statement. - * - * @param statement The statement to release. - */ - public void release(Statement statement); - - /** - * Register a JDBC result set. - * - * @param resultSet The result set to register. - */ - public void register(ResultSet resultSet); - - /** - * Release a previously registered result set. - * - * @param resultSet The result set to release. - */ - public void release(ResultSet resultSet); - - /** - * Does this registry currently have any registered resources? - * - * @return True if the registry does have registered resources; false otherwise. - */ - public boolean hasRegisteredResources(); - - /** - * Release all registered resources. - */ - public void releaseResources(); - - /** - * Close this registry. Also {@link #releaseResources releases} any registered resources. - *

- * After execution, the registry is considered unusable. - */ - public void close(); - - /** - * Register a query statement as being able to be cancelled. - * - * @param statement The cancel-able query statement. - */ - public void registerLastQuery(Statement statement); - - /** - * Cancel the last query registered via {@link #registerLastQuery} - */ - public void cancelLastQuery(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnection.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnection.java index ac546e8647..6741e9a47c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnection.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnection.java @@ -55,27 +55,10 @@ public interface LogicalConnection extends Serializable { * connection has either not yet been obtained (non-UserSuppliedConnectionProvider) * or has previously been aggressively released. * - * @todo ?? Move this to {@link LogicalConnectionImplementor} in lieu of {@link #getShareableConnectionProxy} and {@link #getDistinctConnectionProxy} ?? - * * @return The current Connection. */ public Connection getConnection(); - /** - * Retrieves the shareable connection proxy. - * - * @return The shareable connection proxy. - */ - public Connection getShareableConnectionProxy(); - - /** - * Retrieves a distinct connection proxy. It is distinct in that it is not shared with others unless the caller - * explicitly shares it. - * - * @return The distinct connection proxy. - */ - public Connection getDistinctConnectionProxy(); - /** * Release the underlying connection and clean up any other resources associated * with this logical connection. @@ -85,9 +68,4 @@ public interface LogicalConnection extends Serializable { * @return The application-supplied connection, or {@code null} if Hibernate was managing connection. */ public Connection close(); - - /** - * Signals the end of current transaction in which this logical connection operated. - */ - public void afterTransaction(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnectionImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnectionImplementor.java index 1f942f13db..7d442e7616 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnectionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/LogicalConnectionImplementor.java @@ -25,11 +25,13 @@ package org.hibernate.engine.jdbc.spi; import java.sql.Connection; import org.hibernate.ConnectionReleaseMode; +import org.hibernate.JDBCException; /** * The "internal" contract for LogicalConnection * * @author Steve Ebersole + * @author Brett Meyer */ public interface LogicalConnectionImplementor extends LogicalConnection { /** @@ -39,13 +41,6 @@ public interface LogicalConnectionImplementor extends LogicalConnection { */ public JdbcServices getJdbcServices(); - /** - * Obtains the JDBC resource registry associated with this logical connection. - * - * @return The JDBC resource registry. - */ - public JdbcResourceRegistry getResourceRegistry(); - /** * Add an observer interested in notification of connection events. * @@ -67,30 +62,6 @@ public interface LogicalConnectionImplementor extends LogicalConnection { */ public ConnectionReleaseMode getConnectionReleaseMode(); - /** - * Used to signify that a statement has completed execution which may - * indicate that this logical connection need to perform an - * aggressive release of its physical connection. - */ - public void afterStatementExecution(); - - /** - * Used to signify that a transaction has completed which may indicate - * that this logical connection need to perform an aggressive release - * of its physical connection. - */ - public void afterTransaction(); - - /** - * Manually (and temporarily) circumvent aggressive release processing. - */ - public void disableReleases(); - - /** - * Re-enable aggressive release processing (after a prior {@link #disableReleases()} call. - */ - public void enableReleases(); - /** * Manually disconnect the underlying JDBC Connection. The assumption here * is that the manager will be reconnected at a later point in time. @@ -107,10 +78,14 @@ public interface LogicalConnectionImplementor extends LogicalConnection { * with which to reconnect. It is an error to pass a connection in the other strategies. */ public void manualReconnect(Connection suppliedConnection); + + public void aggressiveRelease(); + + public void releaseConnection() throws JDBCException; public boolean isAutoCommit(); - public boolean isReadyForSerialization(); - public void notifyObserversStatementPrepared(); + + public boolean isUserSuppliedConnection(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ResultSetReturn.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ResultSetReturn.java new file mode 100644 index 0000000000..2dbcc27ed1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ResultSetReturn.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.jdbc.spi; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; + +/** + * Contract for extracting ResultSets from Statements, executing Statements, + * managing Statement/ResultSet resources, and logging statement calls. + * + * TODO: This could eventually utilize the new Return interface. It would be + * great to have a common API shared. + * + * @author Brett Meyer + */ +public interface ResultSetReturn { + + /** + * Extract the ResultSet from the statement. If user passes {@link CallableStatement} + * reference, method calls {@link #extract(CallableStatement)} internally. + * + * @param statement + * + * @return the ResultSet + */ + public ResultSet extract( PreparedStatement statement ); + + /** + * Extract the ResultSet from the statement. + * + * @param statement + * + * @return the ResultSet + */ + public ResultSet extract( CallableStatement statement ); + + /** + * Extract the ResultSet from the statement. + * + * @param statement + * @param sql + * + * @return the ResultSet + */ + public ResultSet extract( Statement statement, String sql ); + + /** + * Execute the Statement query and, if results in a ResultSet, extract it. + * + * @param statement + * + * @return the ResultSet + */ + public ResultSet execute( PreparedStatement statement ); + + /** + * Execute the Statement query and, if results in a ResultSet, extract it. + * + * @param statement + * @param sql + * + * @return the ResultSet + */ + public ResultSet execute( Statement statement, String sql ); + + /** + * Execute the Statement queryUpdate. + * + * @param statement + * + * @return int + */ + public int executeUpdate( PreparedStatement statement ); + + /** + * Execute the Statement query and, if results in a ResultSet, extract it. + * + * @param statement + * @param sql + * + * @return the ResultSet + */ + public int executeUpdate( Statement statement, String sql ); +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/StatementPreparer.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/StatementPreparer.java index eb785d1182..bcefd9daa0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/StatementPreparer.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/StatementPreparer.java @@ -24,6 +24,7 @@ package org.hibernate.engine.jdbc.spi; import java.sql.PreparedStatement; +import java.sql.Statement; import org.hibernate.ScrollMode; @@ -31,8 +32,18 @@ import org.hibernate.ScrollMode; * Contracting for preparing SQL statements * * @author Steve Ebersole + * @author Brett Meyer */ public interface StatementPreparer { + /** + * Create a statement. + * + * @param sql The SQL the statement to be created + * + * @return the statement + */ + public Statement createStatement(); + /** * Prepare a statement. * diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java index 6fdbd0bb78..e661bfbf4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NativeSQLQueryPlan.java @@ -87,7 +87,7 @@ public class NativeSQLQueryPlan implements Serializable { customQuery.getSQL() ); } if ( loc instanceof Integer ) { - return new int[] { ((Integer) loc ).intValue() }; + return new int[] { (Integer) loc }; } else { return ArrayHelper.toIntArray( (List) loc ); @@ -201,11 +201,11 @@ public class NativeSQLQueryPlan implements Serializable { session ); col += bindNamedParameters( ps, queryParameters .getNamedParameters(), col, session ); - result = ps.executeUpdate(); + result = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } finally { if ( ps != null ) { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java index 37d5c8f7d5..240be3fa6d 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java @@ -86,6 +86,8 @@ public class BatchFetchQueue { /** * Clears all entries from this fetch queue. + *

+ * Called after flushing or clearing the session. */ public void clear() { batchLoadableEntityKeys.clear(); @@ -127,16 +129,6 @@ public class BatchFetchQueue { subselectsByEntityKey.remove( key ); } - /** - * Clears all pending subselect fetches from the queue. - *

- * Called after flushing. - */ - public void clearSubselects() { - subselectsByEntityKey.clear(); - } - - // entity batch support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** @@ -291,6 +283,15 @@ public class BatchFetchQueue { for ( Entry me : map.entrySet() ) { final CollectionEntry ce = me.getKey(); final PersistentCollection collection = me.getValue(); + + if ( ce.getLoadedKey() == null ) { + // the loadedKey of the collectionEntry might be null as it might have been reset to null + // (see for example Collections.processDereferencedCollection() + // and CollectionEntry.afterAction()) + // though we clear the queue on flush, it seems like a good idea to guard + // against potentially null loadedKeys (which leads to various NPEs as demonstrated in HHH-7821). + continue; + } if ( collection.wasInitialized() ) { // should never happen diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java index fcd269acba..0f722ed446 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java @@ -264,6 +264,8 @@ public final class CollectionEntry implements Serializable { return snapshot; } + private boolean fromMerge = false; + /** * Reset the stored snapshot for both the persistent collection and this collection entry. * Used during the merge of detached collections. @@ -273,9 +275,14 @@ public final class CollectionEntry implements Serializable { */ public void resetStoredSnapshot(PersistentCollection collection, Serializable storedSnapshot) { LOG.debugf("Reset storedSnapshot to %s for %s", storedSnapshot, this); - + + if ( fromMerge ) { + return; // EARLY EXIT! + } + snapshot = storedSnapshot; - collection.setSnapshot(loadedKey, role, snapshot); + collection.setSnapshot( loadedKey, role, snapshot ); + fromMerge = true; } private void setLoadedPersister(CollectionPersister persister) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/FilterDefinition.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/FilterDefinition.java index f08607923f..d305fbcb4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/FilterDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/FilterDefinition.java @@ -68,7 +68,7 @@ public class FilterDefinition implements Serializable { * * @return The parameters named by this configuration. */ - public Set getParameterNames() { + public Set getParameterNames() { return parameterTypes.keySet(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java index 9cef76b5de..4e73ddce39 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java @@ -56,8 +56,8 @@ public class LoadQueryInfluencers implements Serializable { private final SessionFactoryImplementor sessionFactory; private String internalFetchProfile; - private Map enabledFilters; - private Set enabledFetchProfileNames; + private final Map enabledFilters; + private final Set enabledFetchProfileNames; public LoadQueryInfluencers() { this( null, Collections.emptyMap(), Collections.emptySet() ); @@ -168,7 +168,7 @@ public class LoadQueryInfluencers implements Serializable { // fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean hasEnabledFetchProfiles() { - return enabledFetchProfileNames != null && !enabledFetchProfileNames.isEmpty(); + return !enabledFetchProfileNames.isEmpty(); } public Set getEnabledFetchProfileNames() { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/Managed.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/Managed.java new file mode 100644 index 0000000000..0570605b3c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/Managed.java @@ -0,0 +1,43 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.spi; + +/** + * Contract for classes (specifically, entities and components/embeddables) that are "managed". Developers can + * choose to either have their classes manually implement these interfaces or Hibernate can enhance their classes + * to implement these interfaces via built-time or run-time enhancement. + *

+ * The term managed here is used to describe both:

    + *
  • + * the fact that they are known to the persistence provider (this is defined by the interface itself) + *
  • + *
  • + * its association with Session/EntityManager (this is defined by the state exposed through the interface) + *
  • + *
+ * + * @author Steve Ebersole + */ +public interface Managed { +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureUpdateCountReturn.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedComposite.java similarity index 86% rename from hibernate-core/src/main/java/org/hibernate/StoredProcedureUpdateCountReturn.java rename to hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedComposite.java index e4a9324489..602dbf22ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/StoredProcedureUpdateCountReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedComposite.java @@ -21,11 +21,12 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate; +package org.hibernate.engine.spi; /** + * Specialized {@link Managed} contract for component/embeddable classes. + * * @author Steve Ebersole */ -public interface StoredProcedureUpdateCountReturn extends StoredProcedureReturn { - public int getUpdateCount(); +public interface ManagedComposite extends Managed { } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java new file mode 100644 index 0000000000..e481d61924 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java @@ -0,0 +1,99 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.spi; + +/** + * Specialized {@link Managed} contract for entity classes. Essentially provides access to information + * about an instance's association to a Session/EntityManager. Specific information includes:
    + *
  • + * the association's {@link EntityEntry} (by way of {@link #$$_hibernate_getEntityEntry} and + * {@link #$$_hibernate_setEntityEntry}). EntityEntry describes states, snapshots, etc. + *
  • + *
  • + * link information. ManagedEntity instances are part of a "linked list", thus link information + * describes the next and previous entries/nodes in that ordering. See + * {@link #$$_hibernate_getNextManagedEntity}, {@link #$$_hibernate_setNextManagedEntity}, + * {@link #$$_hibernate_getPreviousManagedEntity}, {@link #$$_hibernate_setPreviousManagedEntity} + *
  • + *
+ * + * @author Steve Ebersole + */ +public interface ManagedEntity extends Managed { + /** + * Obtain a reference to the entity instance. + * + * @return The entity instance. + */ + public Object $$_hibernate_getEntityInstance(); + + /** + * Provides access to the associated EntityEntry. + * + * @return The EntityEntry associated with this entity instance. + * + * @see #$$_hibernate_setEntityEntry + */ + public EntityEntry $$_hibernate_getEntityEntry(); + + /** + * Injects the EntityEntry associated with this entity instance. The EntityEntry represents state associated + * with the entity in regards to its association with a Hibernate Session. + * + * @param entityEntry The EntityEntry associated with this entity instance. + */ + public void $$_hibernate_setEntityEntry(EntityEntry entityEntry); + + /** + * Part of entry linking; obtain reference to the previous entry. Can be {@code null}, which should indicate + * this is the head node. + * + * @return The previous entry + */ + public ManagedEntity $$_hibernate_getPreviousManagedEntity(); + + /** + * Part of entry linking; sets the previous entry. Again, can be {@code null}, which should indicate + * this is (now) the head node. + * + * @param previous The previous entry + */ + public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous); + + /** + * Part of entry linking; obtain reference to the next entry. Can be {@code null}, which should indicate + * this is the tail node. + * + * @return The next entry + */ + public ManagedEntity $$_hibernate_getNextManagedEntity(); + + /** + * Part of entry linking; sets the next entry. Again, can be {@code null}, which should indicate + * this is (now) the tail node. + * + * @param next The next entry + */ + public void $$_hibernate_setNextManagedEntity(ManagedEntity next); +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index c5659ad956..a2b935d577 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -480,12 +480,27 @@ public interface PersistenceContext { * Get the mapping from key value to entity instance */ public Map getEntitiesByKey(); - + + /** + * Provides access to the entity/EntityEntry combos associated with the persistence context in a manner that + * is safe from reentrant access. Specifically, it is safe from additions/removals while iterating. + * + * @return + */ + public Map.Entry[] reentrantSafeEntityEntries(); + /** * Get the mapping from entity instance to entity entry + * + * @deprecated Due to the introduction of EntityEntryContext and bytecode enhancement; only valid really for + * sizing, see {@link #getNumberOfManagedEntities}. For iterating the entity/EntityEntry combos, see + * {@link #reentrantSafeEntityEntries} */ + @Deprecated public Map getEntityEntries(); + public int getNumberOfManagedEntities(); + /** * Get the mapping from collection instance to collection entry */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptable.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptable.java new file mode 100644 index 0000000000..28bd9216e0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptable.java @@ -0,0 +1,32 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.spi; + +/** + * @author Steve Ebersole + */ +public interface PersistentAttributeInterceptable { + public PersistentAttributeInterceptor $$_hibernate_getInterceptor(); + public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor interceptor); +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java new file mode 100644 index 0000000000..f709656ba6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.spi; + +/** + * @author Steve Ebersole + */ +public interface PersistentAttributeInterceptor { + + public boolean readBoolean(Object obj, String name, boolean oldValue); + + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); + + public byte readByte(Object obj, String name, byte oldValue); + + public byte writeByte(Object obj, String name, byte oldValue, byte newValue); + + public char readChar(Object obj, String name, char oldValue); + + public char writeChar(Object obj, String name, char oldValue, char newValue); + + public short readShort(Object obj, String name, short oldValue); + + public short writeShort(Object obj, String name, short oldValue, short newValue); + + public int readInt(Object obj, String name, int oldValue); + + public int writeInt(Object obj, String name, int oldValue, int newValue); + + public float readFloat(Object obj, String name, float oldValue); + + public float writeFloat(Object obj, String name, float oldValue, float newValue); + + public double readDouble(Object obj, String name, double oldValue); + + public double writeDouble(Object obj, String name, double oldValue, double newValue); + + public long readLong(Object obj, String name, long oldValue); + + public long writeLong(Object obj, String name, long oldValue, long newValue); + + public Object readObject(Object obj, String name, Object oldValue); + + public Object writeObject(Object obj, String name, Object oldValue, Object newValue); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java index 3c633decc5..964a15fec4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java @@ -68,6 +68,6 @@ public final class RowSelection { public boolean definesLimits() { return maxRows != null || - ( firstRow != null && firstRow.intValue() <= 0 ); + ( firstRow != null && firstRow <= 0 ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java new file mode 100644 index 0000000000..c91e744973 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -0,0 +1,710 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.spi; + +import java.io.Serializable; +import java.sql.Connection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.hibernate.CacheMode; +import org.hibernate.Criteria; +import org.hibernate.Filter; +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.IdentifierLoadAccess; +import org.hibernate.Interceptor; +import org.hibernate.LobHelper; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.NaturalIdLoadAccess; +import org.hibernate.Query; +import org.hibernate.ReplicationMode; +import org.hibernate.SQLQuery; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.SharedSessionBuilder; +import org.hibernate.SimpleNaturalIdLoadAccess; +import org.hibernate.Transaction; +import org.hibernate.TypeHelper; +import org.hibernate.UnknownProfileException; +import org.hibernate.cache.spi.CacheKey; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; +import org.hibernate.engine.transaction.spi.TransactionCoordinator; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.Work; +import org.hibernate.loader.custom.CustomQuery; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.stat.SessionStatistics; +import org.hibernate.type.Type; + +/** + * This class is meant to be extended. + * + * Wraps and delegates all methods to a {@link SessionImplementor} and + * a {@link Session}. This is useful for custom implementations of this + * API so that only some methods need to be overridden + * (Used by Hibernate Search). + * + * @author Sanne Grinovero (C) 2012 Red Hat Inc. + */ +public class SessionDelegatorBaseImpl implements SessionImplementor, Session { + + protected final SessionImplementor sessionImplementor; + protected final Session session; + + public SessionDelegatorBaseImpl(SessionImplementor sessionImplementor, Session session) { + if ( sessionImplementor == null ) { + throw new IllegalArgumentException( "Unable to create a SessionDelegatorBaseImpl from a null sessionImplementor object" ); + } + if ( session == null ) { + throw new IllegalArgumentException( "Unable to create a SessionDelegatorBaseImpl from a null session object" ); + } + this.sessionImplementor = sessionImplementor; + this.session = session; + } + + // Delegates to SessionImplementor + + @Override + public T execute(Callback callback) { + return sessionImplementor.execute( callback ); + } + + @Override + public String getTenantIdentifier() { + return sessionImplementor.getTenantIdentifier(); + } + + @Override + public JdbcConnectionAccess getJdbcConnectionAccess() { + return sessionImplementor.getJdbcConnectionAccess(); + } + + @Override + public EntityKey generateEntityKey(Serializable id, EntityPersister persister) { + return sessionImplementor.generateEntityKey( id, persister ); + } + + @Override + public CacheKey generateCacheKey(Serializable id, Type type, String entityOrRoleName) { + return sessionImplementor.generateCacheKey( id, type, entityOrRoleName ); + } + + @Override + public Interceptor getInterceptor() { + return sessionImplementor.getInterceptor(); + } + + @Override + public void setAutoClear(boolean enabled) { + sessionImplementor.setAutoClear( enabled ); + } + + @Override + public void disableTransactionAutoJoin() { + sessionImplementor.disableTransactionAutoJoin(); + } + + @Override + public boolean isTransactionInProgress() { + return sessionImplementor.isTransactionInProgress(); + } + + @Override + public void initializeCollection(PersistentCollection collection, boolean writing) throws HibernateException { + sessionImplementor.initializeCollection( collection, writing ); + } + + @Override + public Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) throws HibernateException { + return sessionImplementor.internalLoad( entityName, id, eager, nullable ); + } + + @Override + public Object immediateLoad(String entityName, Serializable id) throws HibernateException { + return sessionImplementor.immediateLoad( entityName, id ); + } + + @Override + public long getTimestamp() { + return sessionImplementor.getTimestamp(); + } + + @Override + public SessionFactoryImplementor getFactory() { + return sessionImplementor.getFactory(); + } + + @Override + public List list(String query, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.list( query, queryParameters ); + } + + @Override + public Iterator iterate(String query, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.iterate( query, queryParameters ); + } + + @Override + public ScrollableResults scroll(String query, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.scroll( query, queryParameters ); + } + + @Override + public ScrollableResults scroll(Criteria criteria, ScrollMode scrollMode) { + return sessionImplementor.scroll( criteria, scrollMode ); + } + + @Override + public List list(Criteria criteria) { + return sessionImplementor.list( criteria ); + } + + @Override + public List listFilter(Object collection, String filter, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.listFilter( collection, filter, queryParameters ); + } + + @Override + public Iterator iterateFilter(Object collection, String filter, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.iterateFilter( collection, filter, queryParameters ); + } + + @Override + public EntityPersister getEntityPersister(String entityName, Object object) throws HibernateException { + return sessionImplementor.getEntityPersister( entityName, object ); + } + + @Override + public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException { + return sessionImplementor.getEntityUsingInterceptor( key ); + } + + @Override + public Serializable getContextEntityIdentifier(Object object) { + return sessionImplementor.getContextEntityIdentifier( object ); + } + + @Override + public String bestGuessEntityName(Object object) { + return sessionImplementor.bestGuessEntityName( object ); + } + + @Override + public String guessEntityName(Object entity) throws HibernateException { + return sessionImplementor.guessEntityName( entity ); + } + + @Override + public Object instantiate(String entityName, Serializable id) throws HibernateException { + return sessionImplementor.instantiate( entityName, id ); + } + + @Override + public List listCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.listCustomQuery( customQuery, queryParameters ); + } + + @Override + public ScrollableResults scrollCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.scrollCustomQuery( customQuery, queryParameters ); + } + + @Override + public List list(NativeSQLQuerySpecification spec, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.list( spec, queryParameters ); + } + + @Override + public ScrollableResults scroll(NativeSQLQuerySpecification spec, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.scroll( spec, queryParameters ); + } + + @Override + public Object getFilterParameterValue(String filterParameterName) { + return sessionImplementor.getFilterParameterValue( filterParameterName ); + } + + @Override + public Type getFilterParameterType(String filterParameterName) { + return sessionImplementor.getFilterParameterType( filterParameterName ); + } + + @Override + public Map getEnabledFilters() { + return sessionImplementor.getEnabledFilters(); + } + + @Override + public int getDontFlushFromFind() { + return sessionImplementor.getDontFlushFromFind(); + } + + @Override + public PersistenceContext getPersistenceContext() { + return sessionImplementor.getPersistenceContext(); + } + + @Override + public int executeUpdate(String query, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.executeUpdate( query, queryParameters ); + } + + @Override + public int executeNativeUpdate(NativeSQLQuerySpecification specification, QueryParameters queryParameters) throws HibernateException { + return sessionImplementor.executeNativeUpdate( specification, queryParameters ); + } + + @Override + public NonFlushedChanges getNonFlushedChanges() throws HibernateException { + return sessionImplementor.getNonFlushedChanges(); + } + + @Override + public void applyNonFlushedChanges(NonFlushedChanges nonFlushedChanges) throws HibernateException { + sessionImplementor.applyNonFlushedChanges( nonFlushedChanges ); + } + + @Override + public CacheMode getCacheMode() { + return sessionImplementor.getCacheMode(); + } + + @Override + public void setCacheMode(CacheMode cm) { + sessionImplementor.setCacheMode( cm ); + } + + @Override + public boolean isOpen() { + return sessionImplementor.isOpen(); + } + + @Override + public boolean isConnected() { + return sessionImplementor.isConnected(); + } + + @Override + public FlushMode getFlushMode() { + return sessionImplementor.getFlushMode(); + } + + @Override + public void setFlushMode(FlushMode fm) { + sessionImplementor.setFlushMode( fm ); + } + + @Override + public Connection connection() { + return sessionImplementor.connection(); + } + + @Override + public void flush() { + sessionImplementor.flush(); + } + + @Override + public Query getNamedQuery(String name) { + return sessionImplementor.getNamedQuery( name ); + } + + @Override + public Query getNamedSQLQuery(String name) { + return sessionImplementor.getNamedSQLQuery( name ); + } + + @Override + public boolean isEventSource() { + return sessionImplementor.isEventSource(); + } + + @Override + public void afterScrollOperation() { + sessionImplementor.afterScrollOperation(); + } + + @Override + public String getFetchProfile() { + return sessionImplementor.getFetchProfile(); + } + + @Override + public void setFetchProfile(String name) { + sessionImplementor.setFetchProfile( name ); + } + + @Override + public TransactionCoordinator getTransactionCoordinator() { + return sessionImplementor.getTransactionCoordinator(); + } + + @Override + public boolean isClosed() { + return sessionImplementor.isClosed(); + } + + @Override + public LoadQueryInfluencers getLoadQueryInfluencers() { + return sessionImplementor.getLoadQueryInfluencers(); + } + + // Delegates to Session + + public Transaction beginTransaction() { + return session.beginTransaction(); + } + + public Transaction getTransaction() { + return session.getTransaction(); + } + + public Query createQuery(String queryString) { + return session.createQuery( queryString ); + } + + public SQLQuery createSQLQuery(String queryString) { + return session.createSQLQuery( queryString ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName) { + return session.createStoredProcedureCall( procedureName ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { + return session.createStoredProcedureCall( procedureName, resultClasses ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { + return session.createStoredProcedureCall( procedureName, resultSetMappings ); + } + + public Criteria createCriteria(Class persistentClass) { + return session.createCriteria( persistentClass ); + } + + public Criteria createCriteria(Class persistentClass, String alias) { + return session.createCriteria( persistentClass, alias ); + } + + public Criteria createCriteria(String entityName) { + return session.createCriteria( entityName ); + } + + public Criteria createCriteria(String entityName, String alias) { + return session.createCriteria( entityName, alias ); + } + + public SharedSessionBuilder sessionWithOptions() { + return session.sessionWithOptions(); + } + + public SessionFactory getSessionFactory() { + return session.getSessionFactory(); + } + + public Connection close() throws HibernateException { + return session.close(); + } + + public void cancelQuery() throws HibernateException { + session.cancelQuery(); + } + + public boolean isDirty() throws HibernateException { + return session.isDirty(); + } + + public boolean isDefaultReadOnly() { + return session.isDefaultReadOnly(); + } + + public void setDefaultReadOnly(boolean readOnly) { + session.setDefaultReadOnly( readOnly ); + } + + public Serializable getIdentifier(Object object) { + return session.getIdentifier( object ); + } + + public boolean contains(Object object) { + return session.contains( object ); + } + + public void evict(Object object) { + session.evict( object ); + } + + public Object load(Class theClass, Serializable id, LockMode lockMode) { + return session.load( theClass, id, lockMode ); + } + + public Object load(Class theClass, Serializable id, LockOptions lockOptions) { + return session.load( theClass, id, lockOptions ); + } + + public Object load(String entityName, Serializable id, LockMode lockMode) { + return session.load( entityName, id, lockMode ); + } + + public Object load(String entityName, Serializable id, LockOptions lockOptions) { + return session.load( entityName, id, lockOptions ); + } + + public Object load(Class theClass, Serializable id) { + return session.load( theClass, id ); + } + + public Object load(String entityName, Serializable id) { + return session.load( entityName, id ); + } + + public void load(Object object, Serializable id) { + session.load( object, id ); + } + + public void replicate(Object object, ReplicationMode replicationMode) { + session.replicate( object, replicationMode ); + } + + public void replicate(String entityName, Object object, ReplicationMode replicationMode) { + session.replicate( entityName, object, replicationMode ); + } + + public Serializable save(Object object) { + return session.save( object ); + } + + public Serializable save(String entityName, Object object) { + return session.save( entityName, object ); + } + + public void saveOrUpdate(Object object) { + session.saveOrUpdate( object ); + } + + public void saveOrUpdate(String entityName, Object object) { + session.saveOrUpdate( entityName, object ); + } + + public void update(Object object) { + session.update( object ); + } + + public void update(String entityName, Object object) { + session.update( entityName, object ); + } + + public Object merge(Object object) { + return session.merge( object ); + } + + public Object merge(String entityName, Object object) { + return session.merge( entityName, object ); + } + + public void persist(Object object) { + session.persist( object ); + } + + public void persist(String entityName, Object object) { + session.persist( entityName, object ); + } + + public void delete(Object object) { + session.delete( object ); + } + + public void delete(String entityName, Object object) { + session.delete( entityName, object ); + } + + public void lock(Object object, LockMode lockMode) { + session.lock( object, lockMode ); + } + + public void lock(String entityName, Object object, LockMode lockMode) { + session.lock( entityName, object, lockMode ); + } + + public LockRequest buildLockRequest(LockOptions lockOptions) { + return session.buildLockRequest( lockOptions ); + } + + public void refresh(Object object) { + session.refresh( object ); + } + + public void refresh(String entityName, Object object) { + session.refresh( entityName, object ); + } + + public void refresh(Object object, LockMode lockMode) { + session.refresh( object, lockMode ); + } + + public void refresh(Object object, LockOptions lockOptions) { + session.refresh( object, lockOptions ); + } + + public void refresh(String entityName, Object object, LockOptions lockOptions) { + session.refresh( entityName, object, lockOptions ); + } + + public LockMode getCurrentLockMode(Object object) { + return session.getCurrentLockMode( object ); + } + + public Query createFilter(Object collection, String queryString) { + return session.createFilter( collection, queryString ); + } + + public void clear() { + session.clear(); + } + + public Object get(Class clazz, Serializable id) { + return session.get( clazz, id ); + } + + public Object get(Class clazz, Serializable id, LockMode lockMode) { + return session.get( clazz, id, lockMode ); + } + + public Object get(Class clazz, Serializable id, LockOptions lockOptions) { + return session.get( clazz, id, lockOptions ); + } + + public Object get(String entityName, Serializable id) { + return session.get( entityName, id ); + } + + public Object get(String entityName, Serializable id, LockMode lockMode) { + return session.get( entityName, id, lockMode ); + } + + public Object get(String entityName, Serializable id, LockOptions lockOptions) { + return session.get( entityName, id, lockOptions ); + } + + public String getEntityName(Object object) { + return session.getEntityName( object ); + } + + public IdentifierLoadAccess byId(String entityName) { + return session.byId( entityName ); + } + + public IdentifierLoadAccess byId(Class entityClass) { + return session.byId( entityClass ); + } + + public NaturalIdLoadAccess byNaturalId(String entityName) { + return session.byNaturalId( entityName ); + } + + public NaturalIdLoadAccess byNaturalId(Class entityClass) { + return session.byNaturalId( entityClass ); + } + + public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName) { + return session.bySimpleNaturalId( entityName ); + } + + public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass) { + return session.bySimpleNaturalId( entityClass ); + } + + public Filter enableFilter(String filterName) { + return session.enableFilter( filterName ); + } + + public Filter getEnabledFilter(String filterName) { + return session.getEnabledFilter( filterName ); + } + + public void disableFilter(String filterName) { + session.disableFilter( filterName ); + } + + public SessionStatistics getStatistics() { + return session.getStatistics(); + } + + public boolean isReadOnly(Object entityOrProxy) { + return session.isReadOnly( entityOrProxy ); + } + + public void setReadOnly(Object entityOrProxy, boolean readOnly) { + session.setReadOnly( entityOrProxy, readOnly ); + } + + public void doWork(Work work) throws HibernateException { + session.doWork( work ); + } + + public T doReturningWork(ReturningWork work) throws HibernateException { + return session.doReturningWork( work ); + } + + public Connection disconnect() { + return session.disconnect(); + } + + public void reconnect(Connection connection) { + session.reconnect( connection ); + } + + public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { + return session.isFetchProfileEnabled( name ); + } + + public void enableFetchProfile(String name) throws UnknownProfileException { + session.enableFetchProfile( name ); + } + + public void disableFetchProfile(String name) throws UnknownProfileException { + session.disableFetchProfile( name ); + } + + public TypeHelper getTypeHelper() { + return session.getTypeHelper(); + } + + public LobHelper getLobHelper() { + return session.getLobHelper(); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java index 8e0f21158a..f81266fa2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import org.hibernate.CacheMode; +import org.hibernate.Criteria; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Interceptor; @@ -170,11 +171,11 @@ public interface SessionImplementor extends Serializable, LobCreationContext { /** * Execute a criteria query */ - public ScrollableResults scroll(CriteriaImpl criteria, ScrollMode scrollMode); + public ScrollableResults scroll(Criteria criteria, ScrollMode scrollMode); /** * Execute a criteria query */ - public List list(CriteriaImpl criteria); + public List list(Criteria criteria); /** * Execute a filter diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/TypedValue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/TypedValue.java index 1b4dfe7313..bb8ed0b594 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/TypedValue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/TypedValue.java @@ -26,6 +26,7 @@ package org.hibernate.engine.spi; import java.io.Serializable; import org.hibernate.EntityMode; +import org.hibernate.internal.util.ValueHolder; import org.hibernate.type.Type; /** @@ -37,16 +38,23 @@ import org.hibernate.type.Type; public final class TypedValue implements Serializable { private final Type type; private final Object value; - private final EntityMode entityMode; + private final ValueHolder hashcode; - public TypedValue(Type type, Object value) { - this( type, value, EntityMode.POJO ); - } - - public TypedValue(Type type, Object value, EntityMode entityMode) { + public TypedValue(final Type type, final Object value) { this.type = type; - this.value=value; - this.entityMode = entityMode; + this.value = value; + this.hashcode = new ValueHolder( + new ValueHolder.DeferredInitializer() { + @Override + public Integer initialize() { + return value == null ? 0 : type.getHashCode( value ); + } + } + ); + } + @Deprecated + public TypedValue(Type type, Object value, EntityMode entityMode) { + this(type, value); } public Object getValue() { @@ -56,19 +64,15 @@ public final class TypedValue implements Serializable { public Type getType() { return type; } - + @Override public String toString() { return value==null ? "null" : value.toString(); } - + @Override public int hashCode() { - //int result = 17; - //result = 37 * result + type.hashCode(); - //result = 37 * result + ( value==null ? 0 : value.hashCode() ); - //return result; - return value==null ? 0 : type.getHashCode(value ); + return hashcode.getValue(); } - + @Override public boolean equals(Object other) { if ( !(other instanceof TypedValue) ) return false; TypedValue that = (TypedValue) other; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionCoordinatorImpl.java index 4fc91f54ed..5d4f2fdacf 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionCoordinatorImpl.java @@ -193,7 +193,7 @@ public class TransactionCoordinatorImpl implements TransactionCoordinator { // check to see if the connection is in auto-commit mode (no connection means aggressive connection // release outside a JTA transaction context, so MUST be autocommit mode) boolean isAutocommit = getJdbcCoordinator().getLogicalConnection().isAutoCommit(); - getJdbcCoordinator().getLogicalConnection().afterTransaction(); + getJdbcCoordinator().afterTransaction(); if ( isAutocommit ) { for ( TransactionObserver observer : observers ) { @@ -267,6 +267,7 @@ public class TransactionCoordinatorImpl implements TransactionCoordinator { } public void pulse() { + getSynchronizationCallbackCoordinator().pulse(); if ( transactionFactory().compatibleWithJtaSynchronization() ) { // the configured transaction strategy says it supports callbacks via JTA synchronization, so attempt to // register JTA synchronization if possible diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java index 0a882c41bf..4f7f8a431d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java @@ -64,7 +64,6 @@ public class JdbcIsolationDelegate implements IsolationDelegate { public T delegateWork(WorkExecutorVisitable work, boolean transacted) throws HibernateException { boolean wasAutoCommit = false; try { - // todo : should we use a connection proxy here? Connection connection = jdbcConnectionAccess().obtainConnection(); try { if ( transacted ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/BitronixJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/BitronixJtaPlatform.java index e6f5f925f9..15a84deef4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/BitronixJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/BitronixJtaPlatform.java @@ -34,7 +34,7 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformException; * @author Steve Ebersole */ public class BitronixJtaPlatform extends AbstractJtaPlatform { - private static final String TM_CLASS_NAME = "bitronix.tm.TransactionManagerServices"; + public static final String TM_CLASS_NAME = "bitronix.tm.TransactionManagerServices"; @Override protected TransactionManager locateTransactionManager() { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java index 9ad1d0b871..a20f292753 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JBossStandAloneJtaPlatform.java @@ -37,8 +37,8 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformException; * @author Steve Ebersole */ public class JBossStandAloneJtaPlatform extends AbstractJtaPlatform { - private static final String JBOSS_TM_CLASS_NAME = "com.arjuna.ats.jta.TransactionManager"; - private static final String JBOSS_UT_CLASS_NAME = "com.arjuna.ats.jta.UserTransaction"; + public static final String JBOSS_TM_CLASS_NAME = "com.arjuna.ats.jta.TransactionManager"; + public static final String JBOSS_UT_CLASS_NAME = "com.arjuna.ats.jta.UserTransaction"; @Override protected TransactionManager locateTransactionManager() { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JOnASJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JOnASJtaPlatform.java index 7c2d16ca04..17bb3efff3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JOnASJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JOnASJtaPlatform.java @@ -36,7 +36,7 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformException; */ public class JOnASJtaPlatform extends AbstractJtaPlatform { public static final String UT_NAME = "java:comp/UserTransaction"; - private static final String TM_CLASS_NAME = "org.objectweb.jonas_tm.Current"; + public static final String TM_CLASS_NAME = "org.objectweb.jonas_tm.Current"; @Override protected TransactionManager locateTransactionManager() { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java index db46f082e4..c3b3226987 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java @@ -31,6 +31,7 @@ import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; +import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformResolver; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -55,8 +56,8 @@ public class JtaPlatformInitiator implements StandardServiceInitiator { + public static final JtaPlatformResolverInitiator INSTANCE = new JtaPlatformResolverInitiator(); + + private static final Logger log = Logger.getLogger( JtaPlatformResolverInitiator.class ); + + @Override + public JtaPlatformResolver initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + final Object setting = configurationValues.get( AvailableSettings.JTA_PLATFORM_RESOLVER ); + final JtaPlatformResolver resolver = registry.getService( StrategySelector.class ) + .resolveStrategy( JtaPlatformResolver.class, setting ); + if ( resolver == null ) { + log.debugf( "No JtaPlatformResolver was specified, using default [%s]", StandardJtaPlatformResolver.class.getName() ); + return StandardJtaPlatformResolver.INSTANCE; + } + return resolver; + } + + @Override + public Class getServiceInitiated() { + return JtaPlatformResolver.class; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/NoJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/NoJtaPlatform.java index 26c3235c75..67593c75f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/NoJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/NoJtaPlatform.java @@ -38,6 +38,8 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; * @author Steve Ebersole */ public class NoJtaPlatform implements JtaPlatform { + public static final NoJtaPlatform INSTANCE = new NoJtaPlatform(); + @Override public TransactionManager retrieveTransactionManager() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/StandardJtaPlatformResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/StandardJtaPlatformResolver.java new file mode 100644 index 0000000000..a81d9bb8e7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/StandardJtaPlatformResolver.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.transaction.jta.platform.internal; + +import java.util.Map; + +import org.jboss.logging.Logger; + +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; +import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformProvider; +import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformResolver; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * @author Steve Ebersole + */ +public class StandardJtaPlatformResolver implements JtaPlatformResolver { + public static final StandardJtaPlatformResolver INSTANCE = new StandardJtaPlatformResolver(); + + private static final Logger log = Logger.getLogger( StandardJtaPlatformResolver.class ); + + @Override + public JtaPlatform resolveJtaPlatform(Map configurationValues, ServiceRegistryImplementor registry) { + final ClassLoaderService classLoaderService = registry.getService( ClassLoaderService.class ); + + // Initially look for a JtaPlatformProvider + for ( JtaPlatformProvider provider : classLoaderService.loadJavaServices( JtaPlatformProvider.class ) ) { + final JtaPlatform providedPlatform = provider.getProvidedJtaPlatform(); + if ( providedPlatform!= null ) { + return providedPlatform; + } + } + + + // look for classes on the ClassLoader (via service) that we know indicate certain JTA impls or + // indicate running in certain environments with known JTA impls. + // + // IMPL NOTE : essentially we attempt Class lookups and use the exceptions from the class(es) not + // being found as the indicator + + + // JBoss ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + try { + classLoaderService.classForName( JBossStandAloneJtaPlatform.JBOSS_TM_CLASS_NAME ); + classLoaderService.classForName( JBossStandAloneJtaPlatform.JBOSS_UT_CLASS_NAME ); + + // we know that the JBoss TS classes are available + // if neither of these look-ups resulted in an error (no such class), then JBossTM is available on + // the classpath + // + // todo : we cannot really distinguish between the need for JBossStandAloneJtaPlatform versus JBossApServerJtaPlatform + // but discussions with David led to the JtaPlatformProvider solution above, so inside JBoss AS we + // should be relying on that + return new JBossStandAloneJtaPlatform(); + } + catch (ClassLoadingException ignore) { + } + + // Bitronix ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + try { + classLoaderService.classForName( BitronixJtaPlatform.TM_CLASS_NAME ); + return new BitronixJtaPlatform(); + } + catch (ClassLoadingException ignore) { + } + + // JOnAS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + try { + classLoaderService.classForName( JOnASJtaPlatform.TM_CLASS_NAME ); + return new JOnASJtaPlatform(); + } + catch (ClassLoadingException ignore) { + } + + // JOTM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + try { + classLoaderService.classForName( JOTMJtaPlatform.TM_CLASS_NAME ); + return new JOTMJtaPlatform(); + } + catch (ClassLoadingException ignore) { + } + + // WebSphere ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for ( WebSphereJtaPlatform.WebSphereEnvironment webSphereEnvironment + : WebSphereJtaPlatform.WebSphereEnvironment.values() ) { + try { + Class accessClass = classLoaderService.classForName( webSphereEnvironment.getTmAccessClassName() ); + return new WebSphereJtaPlatform( accessClass, webSphereEnvironment ); + } + catch (ClassLoadingException ignore) { + } + } + + // Finally, return the default... + log.debugf( "Could not resolve JtaPlatform, using default [%s]", NoJtaPlatform.class.getName() ); + return NoJtaPlatform.INSTANCE; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereJtaPlatform.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereJtaPlatform.java index 9113045d87..9e55cd13a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereJtaPlatform.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/WebSphereJtaPlatform.java @@ -29,7 +29,6 @@ import javax.transaction.UserTransaction; import org.jboss.logging.Logger; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformException; /** @@ -39,45 +38,42 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformException; * @author Steve Ebersole */ public class WebSphereJtaPlatform extends AbstractJtaPlatform { - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, WebSphereJtaPlatform.class.getName()); - - public static final String VERSION_5_UT_NAME = "java:comp/UserTransaction"; - public static final String VERSION_4_UT_NAME = "jta/usertransaction"; + private static final Logger log = Logger.getLogger( WebSphereJtaPlatform.class ); private final Class transactionManagerAccessClass; - private final int webSphereVersion; + private final WebSphereEnvironment webSphereEnvironment; public WebSphereJtaPlatform() { - try { - Class clazz; - int version; - try { - clazz = Class.forName( "com.ibm.ws.Transaction.TransactionManagerFactory" ); - version = 5; - LOG.debug("WebSphere 5.1"); - } - catch ( Exception e ) { - try { - clazz = Class.forName( "com.ibm.ejs.jts.jta.TransactionManagerFactory" ); - version = 5; - LOG.debug("WebSphere 5.0"); - } - catch ( Exception e2 ) { - clazz = Class.forName( "com.ibm.ejs.jts.jta.JTSXA" ); - version = 4; - LOG.debug("WebSphere 4"); - } - } + Class tmAccessClass = null; + WebSphereEnvironment webSphereEnvironment = null; - transactionManagerAccessClass = clazz; - webSphereVersion = version; + for ( WebSphereEnvironment check : WebSphereEnvironment.values() ) { + try { + tmAccessClass = Class.forName( check.getTmAccessClassName() ); + webSphereEnvironment = check; + log.debugf( "WebSphere version : %s", webSphereEnvironment.getWebSphereVersion() ); + break; + } + catch ( Exception ignore ) { + // go on to the next iteration + } } - catch ( Exception e ) { - throw new JtaPlatformException( "Could not locate WebSphere TransactionManager access class", e ); + + if ( webSphereEnvironment == null ) { + throw new JtaPlatformException( "Could not locate WebSphere TransactionManager access class" ); } + + this.transactionManagerAccessClass = tmAccessClass; + this.webSphereEnvironment = webSphereEnvironment; + } + + public WebSphereJtaPlatform(Class transactionManagerAccessClass, WebSphereEnvironment webSphereEnvironment) { + this.transactionManagerAccessClass = transactionManagerAccessClass; + this.webSphereEnvironment = webSphereEnvironment; } @Override + @SuppressWarnings("unchecked") protected TransactionManager locateTransactionManager() { try { final Method method = transactionManagerAccessClass.getMethod( "getTransactionManager" ); @@ -91,7 +87,36 @@ public class WebSphereJtaPlatform extends AbstractJtaPlatform { @Override protected UserTransaction locateUserTransaction() { - final String utName = webSphereVersion == 5 ? VERSION_5_UT_NAME : VERSION_4_UT_NAME; + final String utName = webSphereEnvironment.getUtName(); return (UserTransaction) jndiService().locate( utName ); } + + public static enum WebSphereEnvironment { + WS_4_0( "4.x", "com.ibm.ejs.jts.jta.JTSXA", "jta/usertransaction" ), + WS_5_0( "5.0", "com.ibm.ejs.jts.jta.TransactionManagerFactory", "java:comp/UserTransaction" ), + WS_5_1( "5.1", "com.ibm.ws.Transaction.TransactionManagerFactory", "java:comp/UserTransaction" ) + ; + + private final String webSphereVersion; + private final String tmAccessClassName; + private final String utName; + + private WebSphereEnvironment(String webSphereVersion, String tmAccessClassName, String utName) { + this.webSphereVersion = webSphereVersion; + this.tmAccessClassName = tmAccessClassName; + this.utName = utName; + } + + public String getWebSphereVersion() { + return webSphereVersion; + } + + public String getTmAccessClassName() { + return tmAccessClassName; + } + + public String getUtName() { + return utName; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformProvider.java new file mode 100644 index 0000000000..9eaa274619 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformProvider.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.transaction.jta.platform.spi; + +/** + * A {@link java.util.ServiceLoader}-style provider of {@link JtaPlatform} instances. Used when an + * explicit JtaPlatform is not provided. + * + * @author Steve Ebersole + */ +public interface JtaPlatformProvider { + /** + * Retrieve the JtaPlatform provided by this environment. + * + * @return The provided JtaPlatform + */ + public JtaPlatform getProvidedJtaPlatform(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformResolver.java new file mode 100644 index 0000000000..d7c4a6f335 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/spi/JtaPlatformResolver.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.engine.transaction.jta.platform.spi; + +import java.util.Map; + +import org.hibernate.service.Service; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * Service for defining how to resolve or determine the {@link JtaPlatform} to use in configurations where the user + * did not explicitly specify one. + * + * @author Steve Ebersole + */ +public interface JtaPlatformResolver extends Service { + public JtaPlatform resolveJtaPlatform(Map configurationValues, ServiceRegistryImplementor registry); +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/internal/SynchronizationCallbackCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/internal/SynchronizationCallbackCoordinatorImpl.java index 2ff237498f..6f3de2c254 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/internal/SynchronizationCallbackCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/internal/SynchronizationCallbackCoordinatorImpl.java @@ -25,9 +25,8 @@ package org.hibernate.engine.transaction.synchronization.internal; import javax.transaction.SystemException; -import org.jboss.logging.Logger; - import org.hibernate.TransactionException; +import org.hibernate.cfg.Settings; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; import org.hibernate.engine.transaction.spi.TransactionContext; import org.hibernate.engine.transaction.spi.TransactionCoordinator; @@ -36,31 +35,43 @@ import org.hibernate.engine.transaction.synchronization.spi.ExceptionMapper; import org.hibernate.engine.transaction.synchronization.spi.ManagedFlushChecker; import org.hibernate.engine.transaction.synchronization.spi.SynchronizationCallbackCoordinator; import org.hibernate.internal.CoreMessageLogger; +import org.jboss.logging.Logger; /** * Manages callbacks from the {@link javax.transaction.Synchronization} registered by Hibernate. - * + * * @author Steve Ebersole + * @author Brett Meyer */ public class SynchronizationCallbackCoordinatorImpl implements SynchronizationCallbackCoordinator { - private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, SynchronizationCallbackCoordinatorImpl.class.getName() ); + private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, + SynchronizationCallbackCoordinatorImpl.class.getName() ); private final TransactionCoordinator transactionCoordinator; + private final Settings settings; private ManagedFlushChecker managedFlushChecker; private AfterCompletionAction afterCompletionAction; private ExceptionMapper exceptionMapper; + private long registrationThreadId; + private final int NO_STATUS = -1; + private int delayedCompletionHandlingStatus; + public SynchronizationCallbackCoordinatorImpl(TransactionCoordinator transactionCoordinator) { this.transactionCoordinator = transactionCoordinator; + this.settings = transactionCoordinator.getTransactionContext() + .getTransactionEnvironment().getSessionFactory().getSettings(); reset(); + pulse(); } public void reset() { managedFlushChecker = STANDARD_MANAGED_FLUSH_CHECKER; exceptionMapper = STANDARD_EXCEPTION_MAPPER; afterCompletionAction = STANDARD_AFTER_COMPLETION_ACTION; + delayedCompletionHandlingStatus = NO_STATUS; } @Override @@ -78,7 +89,6 @@ public class SynchronizationCallbackCoordinatorImpl implements SynchronizationCa this.afterCompletionAction = afterCompletionAction; } - // sync callbacks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void beforeCompletion() { @@ -86,16 +96,14 @@ public class SynchronizationCallbackCoordinatorImpl implements SynchronizationCa boolean flush; try { - final int status = transactionCoordinator - .getTransactionContext() - .getTransactionEnvironment() - .getJtaPlatform() - .getCurrentStatus(); + final int status = transactionCoordinator.getTransactionContext().getTransactionEnvironment() + .getJtaPlatform().getCurrentStatus(); flush = managedFlushChecker.shouldDoManagedFlush( transactionCoordinator, status ); } catch ( SystemException se ) { setRollbackOnly(); - throw exceptionMapper.mapStatusCheckFailure( "could not determine transaction status in beforeCompletion()", se ); + throw exceptionMapper.mapStatusCheckFailure( + "could not determine transaction status in beforeCompletion()", se ); } try { @@ -119,6 +127,36 @@ public class SynchronizationCallbackCoordinatorImpl implements SynchronizationCa } public void afterCompletion(int status) { + if ( settings.isJtaTrackByThread() && !isRegistrationThread() + && JtaStatusHelper.isRollback( status ) ) { + // The transaction was rolled back by another thread -- not the + // original application. Examples of this include a JTA transaction + // timeout getting cleaned up by a reaper thread. If this happens, + // afterCompletion must be handled by the original thread in order + // to prevent non-threadsafe session use. Set the flag here and + // check for it in SessionImpl. See HHH-7910. + LOG.warnv( "Transaction afterCompletion called by a background thread! Delaying action until the original thread can handle it. [status={0}]", status ); + delayedCompletionHandlingStatus = status; + } + else { + doAfterCompletion( status ); + } + } + + public void pulse() { + if ( settings.isJtaTrackByThread() ) { + registrationThreadId = Thread.currentThread().getId(); + } + } + + public void delayedAfterCompletion() { + if ( delayedCompletionHandlingStatus != NO_STATUS ) { + doAfterCompletion( delayedCompletionHandlingStatus ); + delayedCompletionHandlingStatus = NO_STATUS; + } + } + + private void doAfterCompletion(int status) { LOG.tracev( "Transaction after completion callback [status={0}]", status ); try { @@ -134,6 +172,10 @@ public class SynchronizationCallbackCoordinatorImpl implements SynchronizationCa } } + private boolean isRegistrationThread() { + return Thread.currentThread().getId() == registrationThreadId; + } + private TransactionContext transactionContext() { return transactionCoordinator.getTransactionContext(); } @@ -141,17 +183,18 @@ public class SynchronizationCallbackCoordinatorImpl implements SynchronizationCa private static final ManagedFlushChecker STANDARD_MANAGED_FLUSH_CHECKER = new ManagedFlushChecker() { @Override public boolean shouldDoManagedFlush(TransactionCoordinator coordinator, int jtaStatus) { - return ! coordinator.getTransactionContext().isClosed() && - ! coordinator.getTransactionContext().isFlushModeNever() && - coordinator.getTransactionContext().isFlushBeforeCompletionEnabled() && - ! JtaStatusHelper.isRollback( jtaStatus ); + return !coordinator.getTransactionContext().isClosed() + && !coordinator.getTransactionContext().isFlushModeNever() + && coordinator.getTransactionContext().isFlushBeforeCompletionEnabled() + && !JtaStatusHelper.isRollback( jtaStatus ); } }; private static final ExceptionMapper STANDARD_EXCEPTION_MAPPER = new ExceptionMapper() { public RuntimeException mapStatusCheckFailure(String message, SystemException systemException) { LOG.error( LOG.unableToDetermineTransactionStatus(), systemException ); - return new TransactionException( "could not determine transaction status in beforeCompletion()", systemException ); + return new TransactionException( "could not determine transaction status in beforeCompletion()", + systemException ); } public RuntimeException mapManagedFlushFailure(String message, RuntimeException failure) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/spi/SynchronizationCallbackCoordinator.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/spi/SynchronizationCallbackCoordinator.java index d96a62b0f9..723aa9d95a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/spi/SynchronizationCallbackCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/synchronization/spi/SynchronizationCallbackCoordinator.java @@ -31,5 +31,7 @@ import javax.transaction.Synchronization; public interface SynchronizationCallbackCoordinator extends Synchronization{ public void setManagedFlushChecker(ManagedFlushChecker managedFlushChecker); public void setAfterCompletionAction(AfterCompletionAction afterCompletionAction); + public void pulse(); + public void delayedAfterCompletion(); public void setExceptionMapper(ExceptionMapper exceptionMapper); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index b64cd9a7ae..509125f14b 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -120,7 +120,7 @@ public abstract class AbstractFlushingEventListener implements Serializable { session.getActionQueue().numberOfInsertions(), session.getActionQueue().numberOfUpdates(), session.getActionQueue().numberOfDeletions(), - persistenceContext.getEntityEntries().size() + persistenceContext.getNumberOfManagedEntities() ); LOG.debugf( "Flushed: %s (re)creations, %s updates, %s removals to %s collections", @@ -145,7 +145,8 @@ public abstract class AbstractFlushingEventListener implements Serializable { final Object anything = getAnything(); //safe from concurrent modification because of how concurrentEntries() is implemented on IdentityMap - for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) { + for ( Map.Entry me : persistenceContext.reentrantSafeEntityEntries() ) { +// for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) { EntityEntry entry = (EntityEntry) me.getValue(); Status status = entry.getStatus(); if ( status == Status.MANAGED || status == Status.SAVING || status == Status.READ_ONLY ) { @@ -213,7 +214,8 @@ public abstract class AbstractFlushingEventListener implements Serializable { // So this needs to be safe from concurrent modification problems. // It is safe because of how IdentityMap implements entrySet() - for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) { + for ( Map.Entry me : persistenceContext.reentrantSafeEntityEntries() ) { +// for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) { // Update the status of the object and if necessary, schedule an update @@ -348,8 +350,10 @@ public abstract class AbstractFlushingEventListener implements Serializable { final PersistenceContext persistenceContext = session.getPersistenceContext(); persistenceContext.getCollectionsByKey().clear(); - persistenceContext.getBatchFetchQueue() - .clearSubselects(); //the database has changed now, so the subselect results need to be invalidated + + // the database has changed now, so the subselect results need to be invalidated + // the batch fetching queues should also be cleared - especially the collection batch fetching one + persistenceContext.getBatchFetchQueue().clear(); for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getCollectionEntries() ) ) { CollectionEntry collectionEntry = me.getValue(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java index 844bfa0a58..235e1711ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java @@ -26,8 +26,6 @@ package org.hibernate.event.internal; import java.io.Serializable; import java.util.Map; -import org.jboss.logging.Logger; - import org.hibernate.LockMode; import org.hibernate.NonUniqueObjectException; import org.hibernate.action.internal.AbstractEntityInsertAction; @@ -51,6 +49,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import org.jboss.logging.Logger; /** * A convenience bas class for listeners responding to save events. @@ -298,6 +297,8 @@ public abstract class AbstractSaveEventListener extends AbstractReassociateEvent insert.getClass().getName() ); } id = ( ( EntityIdentityInsertAction ) insert ).getGeneratedId(); + + insert.handleNaturalIdPostSaveNotifications(id); } markInterceptorDirty( entity, persister, source ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java index fb573f5810..34c70cc374 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java @@ -86,7 +86,7 @@ public class DefaultAutoFlushEventListener extends AbstractFlushingEventListener private boolean flushMightBeNeeded(final EventSource source) { return !source.getFlushMode().lessThan(FlushMode.AUTO) && source.getDontFlushFromFind() == 0 && - ( source.getPersistenceContext().getEntityEntries().size() > 0 || + ( source.getPersistenceContext().getNumberOfManagedEntities() > 0 || source.getPersistenceContext().getCollectionEntries().size() > 0 ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java index 1c58989865..c9c6ea84c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java @@ -62,18 +62,22 @@ public class DefaultEvictEventListener implements EvictEventListener { * @throws HibernateException */ public void onEvict(EvictEvent event) throws HibernateException { - EventSource source = event.getSession(); final Object object = event.getObject(); + if ( object == null ) { + throw new NullPointerException( "null passed to Session.evict()" ); + } + + final EventSource source = event.getSession(); final PersistenceContext persistenceContext = source.getPersistenceContext(); if ( object instanceof HibernateProxy ) { - LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer(); - Serializable id = li.getIdentifier(); - EntityPersister persister = source.getFactory().getEntityPersister( li.getEntityName() ); + final LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer(); + final Serializable id = li.getIdentifier(); if ( id == null ) { - throw new IllegalArgumentException("null identifier"); + throw new IllegalArgumentException( "Could not determine identifier of proxy passed to evict()" ); } + final EntityPersister persister = source.getFactory().getEntityPersister( li.getEntityName() ); final EntityKey key = source.generateEntityKey( id, persister ); persistenceContext.removeProxy( key ); @@ -92,6 +96,23 @@ public class DefaultEvictEventListener implements EvictEventListener { persistenceContext.removeEntity( e.getEntityKey() ); doEvict( object, e.getEntityKey(), e.getPersister(), source ); } + else { + // see if the passed object is even an entity, and if not throw an exception + // this is different than legacy Hibernate behavior, but what JPA 2.1 is calling for + // with EntityManager.detach + EntityPersister persister = null; + final String entityName = persistenceContext.getSession().guessEntityName( object ); + if ( entityName != null ) { + try { + persister = persistenceContext.getSession().getFactory().getEntityPersister( entityName ); + } + catch (Exception ignore) { + } + } + if ( persister == null ) { + throw new IllegalArgumentException( "Non-entity object instance passed to evict : " + object ); + } + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java index 915df116c0..60b3a7657c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java @@ -45,7 +45,7 @@ public class DefaultFlushEventListener extends AbstractFlushingEventListener imp public void onFlush(FlushEvent event) throws HibernateException { final EventSource source = event.getSession(); final PersistenceContext persistenceContext = source.getPersistenceContext(); - if ( persistenceContext.getEntityEntries().size() > 0 || + if ( persistenceContext.getNumberOfManagedEntities() > 0 || persistenceContext.getCollectionEntries().size() > 0 ) { flushEverythingToExecutions(event); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java index 3c2ca3e280..1c39af11b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java @@ -25,6 +25,8 @@ package org.hibernate.event.internal; import java.io.Serializable; +import org.jboss.logging.Logger; + import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.TransientObjectException; @@ -36,6 +38,7 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LockEvent; import org.hibernate.event.spi.LockEventListener; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; /** @@ -46,7 +49,13 @@ import org.hibernate.persister.entity.EntityPersister; */ public class DefaultLockEventListener extends AbstractLockUpgradeEventListener implements LockEventListener { - /** Handle the given lock event. + private static final CoreMessageLogger LOG = Logger.getMessageLogger( + CoreMessageLogger.class, + DefaultLockEventListener.class.getName() + ); + + /** + * Handle the given lock event. * * @param event The lock event to be handled. * @throws HibernateException @@ -61,6 +70,10 @@ public class DefaultLockEventListener extends AbstractLockUpgradeEventListener i throw new HibernateException( "Invalid lock mode for lock()" ); } + if ( event.getLockMode() == LockMode.UPGRADE_SKIPLOCKED ) { + LOG.explicitSkipLockedLockCombo(); + } + SessionImplementor source = event.getSession(); Object entity = source.getPersistenceContext().unproxyAndReassociate( event.getObject() ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlLexer.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlLexer.java index f56c07539c..794eadd7fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlLexer.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlLexer.java @@ -26,11 +26,11 @@ package org.hibernate.hql.internal.ast; import java.io.InputStream; import java.io.Reader; -import antlr.Token; - import org.hibernate.QueryException; import org.hibernate.hql.internal.antlr.HqlBaseLexer; +import antlr.Token; + /** * Custom lexer for the HQL grammar. Extends the base lexer generated by ANTLR * in order to keep the grammar source file clean. @@ -50,20 +50,7 @@ class HqlLexer extends HqlBaseLexer { } public void setTokenObjectClass(String cl) { - Thread thread = null; - ClassLoader contextClassLoader = null; - try { - // workaround HHH-6536, by setting TCCL to the Hibernate classloader - thread = Thread.currentThread(); - contextClassLoader = thread.getContextClassLoader(); - thread.setContextClassLoader(HqlToken.class.getClassLoader()); - - // Ignore the token class name parameter, and use a specific token class. - super.setTokenObjectClass( HqlToken.class.getName() ); - } - finally { - thread.setContextClassLoader( contextClassLoader ); - } + this.tokenObjectClass = HqlToken.class; } protected void setPossibleID(boolean possibleID) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java index d2c6ada9bc..2c3beb7f74 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java @@ -137,7 +137,7 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par * Maps each top-level result variable to its SelectExpression; * (excludes result variables defined in subqueries) **/ - private Map selectExpressionsByResultVariable = new HashMap(); + private Map selectExpressionsByResultVariable = new HashMap(); private Set querySpaces = new HashSet(); @@ -492,8 +492,8 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par private void applyParameterSpecifications(ParameterContainer parameterContainer) { if ( parameterContainer.hasEmbeddedParameters() ) { ParameterSpecification[] specs = parameterContainer.getEmbeddedParameters(); - for ( int i = 0; i < specs.length; i++ ) { - applyParameterSpecification( specs[i] ); + for ( ParameterSpecification spec : specs ) { + applyParameterSpecification( spec ); } } } @@ -1064,7 +1064,7 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par throw qe; } if ( o instanceof Integer ) { - return new int[]{( ( Integer ) o ).intValue()}; + return new int[]{ (Integer) o }; } else { return ArrayHelper.toIntArray( (ArrayList) o ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java index 225545e9e1..915142ffc3 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java @@ -360,15 +360,13 @@ public class QueryTranslatorImpl implements FilterTranslator { // NOTE : firstRow is zero-based int first = !hasLimit || queryParameters.getRowSelection().getFirstRow() == null ? 0 - : queryParameters.getRowSelection().getFirstRow().intValue(); + : queryParameters.getRowSelection().getFirstRow(); int max = !hasLimit || queryParameters.getRowSelection().getMaxRows() == null ? -1 - : queryParameters.getRowSelection().getMaxRows().intValue(); - int size = results.size(); + : queryParameters.getRowSelection().getMaxRows(); List tmp = new ArrayList(); IdentitySet distinction = new IdentitySet(); - for ( int i = 0; i < size; i++ ) { - final Object result = results.get( i ); + for ( final Object result : results ) { if ( !distinction.add( result ) ) { continue; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java index 4973b6b016..0c9b8846b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlGenerator.java @@ -33,6 +33,7 @@ import antlr.RecognitionException; import antlr.collections.AST; import org.jboss.logging.Logger; +import org.hibernate.NullPrecedence; import org.hibernate.QueryException; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -366,4 +367,10 @@ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter { out( d ); } } + + @Override + protected String renderOrderByElement(String expression, String order, String nulls) { + final NullPrecedence nullPrecedence = NullPrecedence.parse( nulls, sessionFactory.getSettings().getDefaultNullPrecedence() ); + return sessionFactory.getDialect().renderOrderByElement( expression, null, order, nullPrecedence ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java index 2c2aeaf164..234e82add8 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java @@ -96,15 +96,15 @@ public class BasicExecutor implements StatementExecutor { } if ( selection != null ) { if ( selection.getTimeout() != null ) { - st.setQueryTimeout( selection.getTimeout().intValue() ); + st.setQueryTimeout( selection.getTimeout() ); } } - return st.executeUpdate(); + return session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ); } finally { if ( st != null ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java index 6bb5e12421..f17dbada6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java @@ -79,7 +79,9 @@ public class InLogicOperatorNode extends BinaryLogicOperatorNode implements Bina if ( !isNodeAcceptable( rhsNode ) ) return; int rhsColumnSpan = 0; - if ( rhsNode.getType() == HqlTokenTypes.VECTOR_EXPR ) { + if ( rhsNode == null ) { + return; // early exit for empty IN list + } else if ( rhsNode.getType() == HqlTokenTypes.VECTOR_EXPR ) { rhsColumnSpan = rhsNode.getNumberOfChildren(); } else { Type rhsType = extractDataType( rhsNode ); @@ -96,7 +98,7 @@ public class InLogicOperatorNode extends BinaryLogicOperatorNode implements Bina * this is possible for parameter lists and explicit lists. It is completely unreasonable for sub-queries. */ private boolean isNodeAcceptable( Node rhsNode ) { - return rhsNode instanceof LiteralNode + return rhsNode == null /* empty IN list */ || rhsNode instanceof LiteralNode || rhsNode instanceof ParameterNode || rhsNode.getType() == HqlTokenTypes.VECTOR_EXPR; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java index c0bfc3a261..167bf0bb6c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTUtil.java @@ -384,8 +384,7 @@ public final class ASTUtil { public static Map generateTokenNameCache(Class tokenTypeInterface) { final Field[] fields = tokenTypeInterface.getFields(); Map cache = new HashMap( (int)( fields.length * .75 ) + 1 ); - for ( int i = 0; i < fields.length; i++ ) { - final Field field = fields[i]; + for ( final Field field : fields ) { if ( Modifier.isStatic( field.getModifiers() ) ) { try { cache.put( field.get( null ), field.getName() ); @@ -429,10 +428,10 @@ public final class ASTUtil { String tokenTypeName = Integer.toString( tokenType ); if ( tokenTypeInterface != null ) { Field[] fields = tokenTypeInterface.getFields(); - for ( int i = 0; i < fields.length; i++ ) { - final Integer fieldValue = extractIntegerValue( fields[i] ); - if ( fieldValue != null && fieldValue.intValue() == tokenType ) { - tokenTypeName = fields[i].getName(); + for ( Field field : fields ) { + final Integer fieldValue = extractIntegerValue( field ); + if ( fieldValue != null && fieldValue == tokenType ) { + tokenTypeName = field.getName(); break; } } @@ -451,7 +450,7 @@ public final class ASTUtil { rtn = ( ( Short ) value ).intValue(); } else if ( value instanceof Long ) { - if ( ( ( Long ) value ).longValue() <= Integer.MAX_VALUE ) { + if ( ( Long ) value <= Integer.MAX_VALUE ) { rtn = ( ( Long ) value ).intValue(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java index 3fb9039b83..a04709af18 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java @@ -551,9 +551,7 @@ public class QueryTranslatorImpl extends BasicLoader implements FilterTranslator qe.setQueryString( queryString ); throw qe; } - if ( o instanceof Integer ) { - return new int[]{ ( ( Integer ) o ).intValue() }; - } + if ( o instanceof Integer ) return new int[] { (Integer) o }; else { return ArrayHelper.toIntArray( ( ArrayList ) o ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java index b328f1136d..a3d6312935 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java @@ -143,8 +143,8 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy { } try { + // TODO: session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); Statement statement = connection.createStatement(); - for ( Table idTableDefinition : idTableDefinitions ) { if ( cleanUpTables ) { if ( tableCleanUpDdl == null ) { @@ -155,6 +155,7 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy { try { final String sql = idTableDefinition.sqlCreateString( jdbcServices.getDialect(), mapping, null, null ); jdbcServices.getSqlStatementLogger().logStatement( sql ); + // TODO: ResultSetExtractor statement.execute( sql ); } catch (SQLException e) { @@ -162,6 +163,8 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy { } } + // TODO +// session.getTransactionCoordinator().getJdbcCoordinator().release( statement ); statement.close(); } catch (SQLException e) { @@ -190,6 +193,7 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy { Connection connection = connectionAccess.obtainConnection(); try { + // TODO: session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); Statement statement = connection.createStatement(); for ( String cleanupDdl : tableCleanUpDdl ) { @@ -202,6 +206,8 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy { } } + // TODO +// session.getTransactionCoordinator().getJdbcCoordinator().release( statement ); statement.close(); } catch (SQLException e) { @@ -266,12 +272,12 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy { try { ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false ); bindSessionIdentifier( ps, session, 1 ); - ps.executeUpdate(); + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } finally { if ( ps != null ) { try { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } catch( Throwable ignore ) { // ignore diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java index 8b51d537b3..73b68866ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java @@ -25,10 +25,9 @@ package org.hibernate.hql.spi; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; -import org.jboss.logging.Logger; - import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -37,8 +36,12 @@ import org.hibernate.hql.internal.ast.tree.DeleteStatement; import org.hibernate.hql.internal.ast.tree.FromElement; import org.hibernate.internal.util.StringHelper; import org.hibernate.param.ParameterSpecification; +import org.hibernate.persister.collection.AbstractCollectionPersister; import org.hibernate.persister.entity.Queryable; import org.hibernate.sql.Delete; +import org.hibernate.type.CollectionType; +import org.hibernate.type.Type; +import org.jboss.logging.Logger; /** * @author Steve Ebersole @@ -52,7 +55,7 @@ public class TableBasedDeleteHandlerImpl private final String idInsertSelect; private final List idSelectParameterSpecifications; - private final String[] deletes; + private final List deletes; public TableBasedDeleteHandlerImpl(SessionFactoryImplementor factory, HqlSqlWalker walker) { this( factory, walker, null, null ); @@ -75,27 +78,42 @@ public class TableBasedDeleteHandlerImpl this.idSelectParameterSpecifications = processedWhereClause.getIdSelectParameterSpecifications(); this.idInsertSelect = generateIdInsertSelect( targetedPersister, bulkTargetAlias, processedWhereClause ); log.tracev( "Generated ID-INSERT-SELECT SQL (multi-table delete) : {0}", idInsertSelect ); + + final String idSubselect = generateIdSubselect( targetedPersister ); + deletes = new ArrayList(); + + // If many-to-many, delete the FK row in the collection table. + for ( Type type : targetedPersister.getPropertyTypes() ) { + if ( type.isCollectionType() ) { + CollectionType cType = (CollectionType) type; + AbstractCollectionPersister cPersister = (AbstractCollectionPersister)factory.getCollectionPersister( cType.getRole() ); + if ( cPersister.isManyToMany() ) { + deletes.add( generateDelete( cPersister.getTableName(), + cPersister.getKeyColumnNames(), idSubselect, "bulk delete - m2m join table cleanup")); + } + } + } String[] tableNames = targetedPersister.getConstraintOrderedTableNameClosure(); String[][] columnNames = targetedPersister.getContraintOrderedTableKeyColumnClosure(); - String idSubselect = generateIdSubselect( targetedPersister ); - - deletes = new String[tableNames.length]; - for ( int i = tableNames.length - 1; i >= 0; i-- ) { + for ( int i = 0; i < tableNames.length; i++ ) { // TODO : an optimization here would be to consider cascade deletes and not gen those delete statements; // the difficulty is the ordering of the tables here vs the cascade attributes on the persisters -> // the table info gotten here should really be self-contained (i.e., a class representation // defining all the needed attributes), then we could then get an array of those - final Delete delete = new Delete() - .setTableName( tableNames[i] ) - .setWhere( "(" + StringHelper.join( ", ", columnNames[i] ) + ") IN (" + idSubselect + ")" ); - if ( factory().getSettings().isCommentsEnabled() ) { - delete.setComment( "bulk delete" ); - } - - deletes[i] = delete.toStatementString(); + deletes.add( generateDelete( tableNames[i], columnNames[i], idSubselect, "bulk delete")); } } + + private String generateDelete(String tableName, String[] columnNames, String idSubselect, String comment) { + final Delete delete = new Delete() + .setTableName( tableName ) + .setWhere( "(" + StringHelper.join( ", ", columnNames ) + ") IN (" + idSubselect + ")" ); + if ( factory().getSettings().isCommentsEnabled() ) { + delete.setComment( comment ); + } + return delete.toStatementString(); + } @Override public Queryable getTargetedQueryable() { @@ -104,7 +122,7 @@ public class TableBasedDeleteHandlerImpl @Override public String[] getSqlStatements() { - return deletes; + return deletes.toArray( new String[deletes.size()] ); } @Override @@ -121,11 +139,11 @@ public class TableBasedDeleteHandlerImpl for ( ParameterSpecification parameterSpecification : idSelectParameterSpecifications ) { pos += parameterSpecification.bind( ps, queryParameters, session, pos ); } - resultCount = ps.executeUpdate(); + resultCount = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } finally { if ( ps != null ) { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } } @@ -142,11 +160,11 @@ public class TableBasedDeleteHandlerImpl .getStatementPreparer() .prepareStatement( delete, false ); handleAddedParametersOnDelete( ps, session ); - ps.executeUpdate(); + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } finally { if ( ps != null ) { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedUpdateHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedUpdateHandlerImpl.java index 5b2a990213..7e2f2b634b 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedUpdateHandlerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedUpdateHandlerImpl.java @@ -141,11 +141,11 @@ public class TableBasedUpdateHandlerImpl for ( ParameterSpecification parameterSpecification : idSelectParameterSpecifications ) { sum += parameterSpecification.bind( ps, queryParameters, session, sum ); } - resultCount = ps.executeUpdate(); + resultCount = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } finally { if ( ps != null ) { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } } @@ -168,11 +168,11 @@ public class TableBasedUpdateHandlerImpl } handleAddedParametersOnUpdate( ps, session, position ); } - ps.executeUpdate(); + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } finally { if ( ps != null ) { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } } @@ -193,6 +193,5 @@ public class TableBasedUpdateHandlerImpl } protected void handleAddedParametersOnUpdate(PreparedStatement ps, SessionImplementor session, int position) throws SQLException { - //To change body of created methods use File | Settings | File Templates. } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/TemporaryTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/TemporaryTableBulkIdStrategy.java index 58ff8098c4..901ec1f0df 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/TemporaryTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/TemporaryTableBulkIdStrategy.java @@ -110,11 +110,10 @@ public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy { final Connection connection = session.getTransactionCoordinator() .getJdbcCoordinator() .getLogicalConnection() - .getShareableConnectionProxy(); + .getConnection(); work.execute( connection ); session.getTransactionCoordinator() .getJdbcCoordinator() - .getLogicalConnection() .afterStatementExecution(); } } @@ -132,11 +131,10 @@ public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy { final Connection connection = session.getTransactionCoordinator() .getJdbcCoordinator() .getLogicalConnection() - .getShareableConnectionProxy(); + .getConnection(); work.execute( connection ); session.getTransactionCoordinator() .getJdbcCoordinator() - .getLogicalConnection() .afterStatementExecution(); } } @@ -146,7 +144,7 @@ public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy { try { final String sql = "delete from " + persister.getTemporaryIdTableName(); ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false ); - ps.executeUpdate(); + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); } catch( Throwable t ) { log.unableToCleanupTemporaryIdTable(t); @@ -154,7 +152,7 @@ public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy { finally { if ( ps != null ) { try { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } catch( Throwable ignore ) { // ignore @@ -246,7 +244,6 @@ public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy { try { Statement statement = connection.createStatement(); try { - statement = connection.createStatement(); statement.executeUpdate( command ); } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/id/GUIDGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/GUIDGenerator.java index 6f9862c087..d1b4053d2a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/GUIDGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/GUIDGenerator.java @@ -57,20 +57,20 @@ public class GUIDGenerator implements IdentifierGenerator { try { PreparedStatement st = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql ); try { - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); final String result; try { rs.next(); result = rs.getString(1); } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } LOG.guidGenerated(result); return result; } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch (SQLException sqle) { diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java index 57a60166ce..4f59cd2575 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java @@ -92,8 +92,8 @@ public class IdentityGenerator extends AbstractPostInsertGenerator { .prepareStatement( insertSQL, PreparedStatement.RETURN_GENERATED_KEYS ); } - public Serializable executeAndExtract(PreparedStatement insert) throws SQLException { - insert.executeUpdate(); + public Serializable executeAndExtract(PreparedStatement insert, SessionImplementor session) throws SQLException { + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( insert ); ResultSet rs = null; try { rs = insert.getGeneratedKeys(); @@ -105,7 +105,7 @@ public class IdentityGenerator extends AbstractPostInsertGenerator { } finally { if ( rs != null ) { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } } @@ -140,13 +140,8 @@ public class IdentityGenerator extends AbstractPostInsertGenerator { .prepareStatement( insertSQL, PreparedStatement.NO_GENERATED_KEYS ); } - public Serializable executeAndExtract(PreparedStatement insert) throws SQLException { - if ( !insert.execute() ) { - while ( !insert.getMoreResults() && insert.getUpdateCount() != -1 ) { - // do nothing until we hit the rsult set containing the generated id - } - } - ResultSet rs = insert.getResultSet(); + public Serializable executeAndExtract(PreparedStatement insert, SessionImplementor session) throws SQLException { + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().execute( insert ); try { return IdentifierGeneratorHelper.getGeneratedIdentity( rs, @@ -155,7 +150,7 @@ public class IdentityGenerator extends AbstractPostInsertGenerator { ); } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java index a116034566..7e9467f5d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IncrementGenerator.java @@ -126,7 +126,7 @@ public class IncrementGenerator implements IdentifierGenerator, Configurable { try { PreparedStatement st = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql ); try { - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { if (rs.next()) previousValueHolder.initialize(rs, 0L).increment(); else previousValueHolder.initialize(1L); @@ -136,11 +136,11 @@ public class IncrementGenerator implements IdentifierGenerator, Configurable { } } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch (SQLException sqle) { diff --git a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java index a556c5ba26..74ca3bb7fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java @@ -124,6 +124,7 @@ public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenera private Class returnClass; private int keySize; + public Object generatorKey() { return tableName; } @@ -307,15 +308,8 @@ public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenera }; } + public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - StringBuilder sqlDropString = new StringBuilder( "drop table " ); - if ( dialect.supportsIfExistsBeforeTableName() ) { - sqlDropString.append( "if exists " ); - } - sqlDropString.append( tableName ).append( dialect.getCascadeConstraintsString() ); - if ( dialect.supportsIfExistsAfterTableName() ) { - sqlDropString.append( " if exists" ); - } - return new String[] { sqlDropString.toString() }; + return new String[] { dialect.getDropTableString( tableName ) }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java index 8ae8bcfd20..a96e8b0fdc 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java @@ -96,11 +96,4 @@ public interface PersistentIdentifierGenerator extends IdentifierGenerator, Expo * @return Object an identifying key for this generator */ public Object generatorKey(); - } - - - - - - diff --git a/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java index f879da29cb..88ef16b22e 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java @@ -125,7 +125,7 @@ public class SequenceGenerator try { PreparedStatement st = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql ); try { - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { rs.next(); IntegralDataTypeHolder result = buildHolder(); @@ -134,11 +134,11 @@ public class SequenceGenerator return result; } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java index 6e838cfbe5..df2c3e99b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SequenceIdentityGenerator.java @@ -99,8 +99,8 @@ public class SequenceIdentityGenerator } @Override - protected Serializable executeAndExtract(PreparedStatement insert) throws SQLException { - insert.executeUpdate(); + protected Serializable executeAndExtract(PreparedStatement insert, SessionImplementor session) throws SQLException { + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( insert ); return IdentifierGeneratorHelper.getGeneratedIdentity( insert.getGeneratedKeys(), getPersister().getRootTableKeyColumnNames()[0], diff --git a/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java index 1b37abb5a1..1b4e3b8163 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java @@ -141,7 +141,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab return generateHolder( session ).makeValue(); } - protected IntegralDataTypeHolder generateHolder(SessionImplementor session) { + protected IntegralDataTypeHolder generateHolder(final SessionImplementor session) { final SqlStatementLogger statementLogger = session .getFactory() .getServiceRegistry() @@ -179,7 +179,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab } statementLogger.logStatement( update, FormatStyle.BASIC.getFormatter() ); - PreparedStatement ups = connection.prepareStatement(update); + PreparedStatement ups = connection.prepareStatement( update ); try { value.copy().increment().bind( ups, 1 ); value.bind( ups, 2 ); @@ -214,15 +214,7 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab } public String[] sqlDropStrings(Dialect dialect) { - StringBuilder sqlDropString = new StringBuilder( "drop table " ); - if ( dialect.supportsIfExistsBeforeTableName() ) { - sqlDropString.append( "if exists " ); - } - sqlDropString.append( tableName ).append( dialect.getCascadeConstraintsString() ); - if ( dialect.supportsIfExistsAfterTableName() ) { - sqlDropString.append( " if exists" ); - } - return new String[] { sqlDropString.toString() }; + return new String[] { dialect.getDropTableString( tableName ) }; } public Object generatorKey() { diff --git a/hibernate-core/src/main/java/org/hibernate/id/TableHiLoGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/TableHiLoGenerator.java index 0fe513be23..3d821731d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/TableHiLoGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/TableHiLoGenerator.java @@ -49,7 +49,7 @@ import org.hibernate.type.Type; * @see SequenceHiLoGenerator * @author Gavin King * - * @deprecate use {@link SequenceStyleGenerator} instead. + * @deprecated use {@link SequenceStyleGenerator} instead. */ @Deprecated public class TableHiLoGenerator extends TableGenerator { diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java index 8b6dd23f3b..ff18501c87 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java @@ -99,7 +99,7 @@ public class SequenceStructure implements DatabaseStructure { try { PreparedStatement st = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql ); try { - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { rs.next(); IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( numberType ); @@ -111,7 +111,7 @@ public class SequenceStructure implements DatabaseStructure { } finally { try { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } catch( Throwable ignore ) { // intentionally empty @@ -119,7 +119,7 @@ public class SequenceStructure implements DatabaseStructure { } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java index 707bbf66a3..69d3f8f9f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java @@ -602,14 +602,6 @@ public class TableGenerator implements PersistentIdentifierGenerator, Configurab @Override public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - StringBuilder sqlDropString = new StringBuilder().append( "drop table " ); - if ( dialect.supportsIfExistsBeforeTableName() ) { - sqlDropString.append( "if exists " ); - } - sqlDropString.append( tableName ).append( dialect.getCascadeConstraintsString() ); - if ( dialect.supportsIfExistsAfterTableName() ) { - sqlDropString.append( " if exists" ); - } - return new String[] { sqlDropString.toString() }; + return new String[] { dialect.getDropTableString( tableName ) }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java index 634ad4bcb7..d932302e6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java @@ -219,15 +219,7 @@ public class TableStructure implements DatabaseStructure { @Override public String[] sqlDropStrings(Dialect dialect) throws HibernateException { - StringBuilder sqlDropString = new StringBuilder().append( "drop table " ); - if ( dialect.supportsIfExistsBeforeTableName() ) { - sqlDropString.append( "if exists " ); - } - sqlDropString.append( tableName ).append( dialect.getCascadeConstraintsString() ); - if ( dialect.supportsIfExistsAfterTableName() ) { - sqlDropString.append( " if exists" ); - } - return new String[] { sqlDropString.toString() }; + return new String[] { dialect.getDropTableString( tableName ) }; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java index 1f934024c0..aa8649ce9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java @@ -55,7 +55,7 @@ public abstract class AbstractReturningDelegate implements InsertGeneratedIdenti PreparedStatement insert = prepare( insertSQL, session ); try { binder.bindValues( insert ); - return executeAndExtract( insert ); + return executeAndExtract( insert, session ); } finally { releaseStatement( insert, session ); @@ -76,9 +76,9 @@ public abstract class AbstractReturningDelegate implements InsertGeneratedIdenti protected abstract PreparedStatement prepare(String insertSQL, SessionImplementor session) throws SQLException; - protected abstract Serializable executeAndExtract(PreparedStatement insert) throws SQLException; + protected abstract Serializable executeAndExtract(PreparedStatement insert, SessionImplementor session) throws SQLException; protected void releaseStatement(PreparedStatement insert, SessionImplementor session) throws SQLException { - insert.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( insert ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java index aaf3dc9bec..8af7a758c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java @@ -58,10 +58,10 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti .prepareStatement( insertSQL, PreparedStatement.NO_GENERATED_KEYS ); try { binder.bindValues( insert ); - insert.executeUpdate(); + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( insert ); } finally { - insert.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( insert ); } } catch ( SQLException sqle ) { @@ -82,16 +82,16 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti .prepareStatement( selectSQL, false ); try { bindParameters( session, idSelect, binder.getEntity() ); - ResultSet rs = idSelect.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( idSelect ); try { return getResult( session, rs, binder.getEntity() ); } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - idSelect.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( idSelect ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java index f78b8a2fba..290d0a36ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java @@ -39,6 +39,7 @@ import org.hibernate.internal.util.BytesHelper; * @author Steve Ebersole */ public class CustomVersionOneStrategy implements UUIDGenerationStrategy { + @Override public int getGeneratedVersion() { return 1; } @@ -58,7 +59,7 @@ public class CustomVersionOneStrategy implements UUIDGenerationStrategy { mostSignificantBits = BytesHelper.asLong( hiBits ); } - + @Override public UUID generateUUID(SessionImplementor session) { long leastSignificantBits = generateLeastSignificantBits( System.currentTimeMillis() ); return new UUID( mostSignificantBits, leastSignificantBits ); diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java index 13b6ea48a0..fe9d50061a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java @@ -38,6 +38,7 @@ public class StandardRandomStrategy implements UUIDGenerationStrategy { /** * A variant 4 (random) strategy */ + @Override public int getGeneratedVersion() { // a "random" strategy return 4; @@ -46,6 +47,7 @@ public class StandardRandomStrategy implements UUIDGenerationStrategy { /** * Delegates to {@link UUID#randomUUID()} */ + @Override public UUID generateUUID(SessionImplementor session) { return UUID.randomUUID(); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractScrollableResults.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractScrollableResults.java index 0d4895a7bf..684fe5806d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractScrollableResults.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractScrollableResults.java @@ -115,26 +115,16 @@ public abstract class AbstractScrollableResults implements ScrollableResults { } public final void close() throws HibernateException { + // not absolutely necessary, but does help with aggressive release + //session.getJDBCContext().getConnectionManager().closeQueryStatement( ps, resultSet ); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); try { - // not absolutely necessary, but does help with aggressive release - //session.getJDBCContext().getConnectionManager().closeQueryStatement( ps, resultSet ); - ps.close(); + session.getPersistenceContext().getLoadContexts().cleanup( resultSet ); } - catch (SQLException sqle) { - throw session.getFactory().getSQLExceptionHelper().convert( - sqle, - "could not close results" - ); - } - finally { - try { - session.getPersistenceContext().getLoadContexts().cleanup( resultSet ); - } - catch( Throwable ignore ) { - // ignore this error for now - if ( LOG.isTraceEnabled() ) { - LOG.tracev( "Exception trying to cleanup load context : {0}", ignore.getMessage() ); - } + catch( Throwable ignore ) { + // ignore this error for now + if ( LOG.isTraceEnabled() ) { + LOG.tracev( "Exception trying to cleanup load context : {0}", ignore.getMessage() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java index 67436b9ced..a205dc01a6 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java @@ -37,7 +37,7 @@ import org.hibernate.SQLQuery; import org.hibernate.ScrollableResults; import org.hibernate.SessionException; import org.hibernate.SharedSessionContract; -import org.hibernate.StoredProcedureCall; +import org.hibernate.procedure.ProcedureCall; import org.hibernate.cache.spi.CacheKey; import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.query.spi.HQLQueryPlan; @@ -59,6 +59,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.procedure.internal.ProcedureCallImpl; import org.hibernate.type.Type; /** @@ -192,10 +193,10 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession query.setReadOnly( nqd.isReadOnly() ); if ( nqd.getTimeout() != null ) { - query.setTimeout( nqd.getTimeout().intValue() ); + query.setTimeout( nqd.getTimeout() ); } if ( nqd.getFetchSize() != null ) { - query.setFetchSize( nqd.getFetchSize().intValue() ); + query.setFetchSize( nqd.getFetchSize() ); } if ( nqd.getCacheMode() != null ) { query.setCacheMode( nqd.getCacheMode() ); @@ -240,29 +241,29 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession @Override @SuppressWarnings("UnnecessaryLocalVariable") - public StoredProcedureCall createStoredProcedureCall(String procedureName) { + public ProcedureCall createStoredProcedureCall(String procedureName) { errorIfClosed(); - final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName ); + final ProcedureCall procedureCall = new ProcedureCallImpl( this, procedureName ); // call.setComment( "Dynamic stored procedure call" ); - return call; + return procedureCall; } @Override @SuppressWarnings("UnnecessaryLocalVariable") - public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { + public ProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { errorIfClosed(); - final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName, resultClasses ); + final ProcedureCall procedureCall = new ProcedureCallImpl( this, procedureName, resultClasses ); // call.setComment( "Dynamic stored procedure call" ); - return call; + return procedureCall; } @Override @SuppressWarnings("UnnecessaryLocalVariable") - public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { + public ProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { errorIfClosed(); - final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName, resultSetMappings ); + final ProcedureCall procedureCall = new ProcedureCallImpl( this, procedureName, resultSetMappings ); // call.setComment( "Dynamic stored procedure call" ); - return call; + return procedureCall; } protected HQLQueryPlan getHQLQueryPlan(String query, boolean shallow) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java b/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java index eb6775d2ef..2770a5c24d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ConnectionObserverStatsBridge.java @@ -41,7 +41,7 @@ public class ConnectionObserverStatsBridge implements ConnectionObserver, Serial @Override public void physicalConnectionObtained(Connection connection) { - if (sessionFactory.getStatistics().isStatisticsEnabled()) { + if ( sessionFactory.getStatistics().isStatisticsEnabled() ) { sessionFactory.getStatisticsImplementor().connect(); } } @@ -56,7 +56,7 @@ public class ConnectionObserverStatsBridge implements ConnectionObserver, Serial @Override public void statementPrepared() { - if (sessionFactory.getStatistics().isStatisticsEnabled()) { + if ( sessionFactory.getStatistics().isStatisticsEnabled() ) { sessionFactory.getStatisticsImplementor().prepareStatement(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index a32800caca..43ea3aa0a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -48,12 +48,12 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.cache.CacheException; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; import org.hibernate.engine.loading.internal.CollectionLoadContext; import org.hibernate.engine.loading.internal.EntityLoadContext; import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.id.IntegralDataTypeHolder; -import org.hibernate.engine.jdbc.dialect.internal.AbstractDialectResolver; import org.hibernate.engine.jndi.JndiException; import org.hibernate.engine.jndi.JndiNameException; import org.hibernate.type.BasicType; @@ -1076,7 +1076,7 @@ public interface CoreMessageLogger extends BasicLogger { @LogMessage(level = WARN) @Message(value = "Error executing resolver [%s] : %s", id = 316) - void unableToExecuteResolver(AbstractDialectResolver abstractDialectResolver, + void unableToExecuteResolver(DialectResolver abstractDialectResolver, String message); @LogMessage(level = INFO) @@ -1650,4 +1650,15 @@ public interface CoreMessageLogger extends BasicLogger { id = 456 ) void embedXmlAttributesNoLongerSupported(); + + @LogMessage(level = WARN) + @Message( + value = "Explicit use of UPGRADE_SKIPLOCKED in lock() calls is not recommended; use normal UPGRADE locking instead", + id = 447 + ) + void explicitSkipLockedLockCombo(); + + @LogMessage(level = INFO) + @Message( value = "'javax.persistence.validation.mode' named multiple values : %s", id = 448 ) + void multipleValidationModes(String modes); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java index cb34b9129a..62dcf63a7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FetchingScrollableResultsImpl.java @@ -67,9 +67,9 @@ public class FetchingScrollableResultsImpl extends AbstractScrollableResults { * @return true if there is another result */ public boolean next() throws HibernateException { - if ( maxPosition != null && maxPosition.intValue() <= currentPosition ) { + if ( maxPosition != null && maxPosition <= currentPosition ) { currentRow = null; - currentPosition = maxPosition.intValue() + 1; + currentPosition = maxPosition + 1; return false; } @@ -130,7 +130,7 @@ public class FetchingScrollableResultsImpl extends AbstractScrollableResults { getSession(), getQueryParameters(), false, - ( maxPosition != null && currentPosition > maxPosition.intValue() ) + ( maxPosition != null && currentPosition > maxPosition ) ); currentRow = new Object[] { loadResult }; @@ -186,10 +186,10 @@ public class FetchingScrollableResultsImpl extends AbstractScrollableResults { public boolean last() throws HibernateException { boolean more = false; if ( maxPosition != null ) { - if ( currentPosition > maxPosition.intValue() ) { + if ( currentPosition > maxPosition ) { more = previous(); } - for ( int i = currentPosition; i < maxPosition.intValue(); i++ ) { + for ( int i = currentPosition; i < maxPosition; i++ ) { more = next(); } } @@ -284,7 +284,7 @@ public class FetchingScrollableResultsImpl extends AbstractScrollableResults { return false; } else { - return currentPosition == maxPosition.intValue(); + return currentPosition == maxPosition; } } @@ -312,7 +312,7 @@ public class FetchingScrollableResultsImpl extends AbstractScrollableResults { else if ( rowNumber == -1 ) { return last(); } - else if ( maxPosition != null && rowNumber == maxPosition.intValue() ) { + else if ( maxPosition != null && rowNumber == maxPosition ) { return last(); } return scroll( rowNumber - currentPosition ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java index 601a02cf66..283650b68c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FilterImpl.java @@ -120,7 +120,7 @@ public class FilterImpl implements Filter, Serializable { if ( type == null ) { throw new HibernateException( "Undefined filter parameter [" + name + "]" ); } - if ( values.size() > 0 ) { + if ( !values.isEmpty() ) { Class elementClass = values.iterator().next().getClass(); if ( !type.getReturnedClass().isAssignableFrom( elementClass ) ) { throw new HibernateException( "Incorrect type for parameter [" + name + "]" ); @@ -161,9 +161,8 @@ public class FilterImpl implements Filter, Serializable { public void validate() throws HibernateException { // for each of the defined parameters, make sure its value // has been set - Iterator itr = definition.getParameterNames().iterator(); - while ( itr.hasNext() ) { - final String parameterName = (String) itr.next(); + + for ( final String parameterName : definition.getParameterNames() ) { if ( parameters.get( parameterName ) == null ) { throw new HibernateException( "Filter [" + getName() + "] parameter [" + parameterName + "] value not set" diff --git a/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java index 54d4fd42eb..588ffdabdb 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java @@ -83,28 +83,17 @@ public final class IteratorImpl implements HibernateIterator { public void close() throws JDBCException { if (ps!=null) { + LOG.debug("Closing iterator"); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); + ps = null; + rs = null; + hasNext = false; try { - LOG.debug("Closing iterator"); - ps.close(); - ps = null; - rs = null; - hasNext = false; + session.getPersistenceContext().getLoadContexts().cleanup( rs ); } - catch (SQLException e) { - LOG.unableToCloseIterator(e); - throw session.getFactory().getSQLExceptionHelper().convert( - e, - "Unable to close iterator" - ); - } - finally { - try { - session.getPersistenceContext().getLoadContexts().cleanup( rs ); - } - catch( Throwable ignore ) { - // ignore this error for now - LOG.debugf("Exception trying to cleanup load context : %s", ignore.getMessage()); - } + catch( Throwable ignore ) { + // ignore this error for now + LOG.debugf("Exception trying to cleanup load context : %s", ignore.getMessage()); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 0d2b2b3ed4..950e849b42 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -43,7 +43,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.naming.Reference; import javax.naming.StringRefAddr; import javax.persistence.metamodel.Metamodel; - import org.hibernate.AssertionFailure; import org.hibernate.Cache; import org.hibernate.ConnectionReleaseMode; @@ -117,6 +116,7 @@ import org.hibernate.engine.spi.SessionOwner; import org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.engine.transaction.spi.TransactionEnvironment; +import org.hibernate.engine.transaction.spi.TransactionFactory; import org.hibernate.exception.spi.SQLExceptionConverter; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.UUIDGenerator; @@ -1399,14 +1399,16 @@ public final class SessionFactoryImpl return results.toArray( new String[results.size()] ); } + @Override public String getImportedClassName(String className) { - String result = imports.get(className); - if (result==null) { + String result = imports.get( className ); + if ( result == null ) { try { serviceRegistry.getService( ClassLoaderService.class ).classForName( className ); + imports.put( className, className ); return className; } - catch (ClassLoadingException cnfe) { + catch ( ClassLoadingException cnfe ) { return null; } } @@ -1588,8 +1590,8 @@ public final class SessionFactoryImpl return identifierGenerators.get(rootEntityName); } - private org.hibernate.engine.transaction.spi.TransactionFactory transactionFactory() { - return serviceRegistry.getService( org.hibernate.engine.transaction.spi.TransactionFactory.class ); + private TransactionFactory transactionFactory() { + return serviceRegistry.getService( TransactionFactory.class ); } private boolean canAccessTransactionManager() { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 025d175445..cc5ba868b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -47,8 +47,6 @@ import java.util.Set; import javax.persistence.EntityNotFoundException; -import org.jboss.logging.Logger; - import org.hibernate.AssertionFailure; import org.hibernate.CacheMode; import org.hibernate.ConnectionReleaseMode; @@ -78,7 +76,6 @@ import org.hibernate.SessionBuilder; import org.hibernate.SessionException; import org.hibernate.SharedSessionBuilder; import org.hibernate.SimpleNaturalIdLoadAccess; -import org.hibernate.StoredProcedureCall; import org.hibernate.Transaction; import org.hibernate.TransientObjectException; import org.hibernate.TypeHelper; @@ -152,12 +149,14 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.pretty.MessageHelper; +import org.hibernate.procedure.ProcedureCall; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.stat.SessionStatistics; import org.hibernate.stat.internal.SessionStatisticsImpl; import org.hibernate.type.SerializationException; import org.hibernate.type.Type; +import org.jboss.logging.Logger; /** * Concrete implementation of a Session. @@ -171,6 +170,7 @@ import org.hibernate.type.Type; * * @author Gavin King * @author Steve Ebersole + * @author Brett Meyer */ public final class SessionImpl extends AbstractSessionImpl implements EventSource { @@ -322,7 +322,9 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc public void clear() { errorIfClosed(); - checkTransactionSynchStatus(); + // Do not call checkTransactionSynchStatus() here -- if a delayed + // afterCompletion exists, it can cause an infinite loop. + pulseTransactionCoordinator(); internalClear(); } @@ -545,7 +547,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc public Connection connection() throws HibernateException { errorIfClosed(); - return transactionCoordinator.getJdbcCoordinator().getLogicalConnection().getDistinctConnectionProxy(); + return transactionCoordinator.getJdbcCoordinator().getLogicalConnection().getConnection(); } public boolean isConnected() { @@ -562,6 +564,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc public Connection disconnect() throws HibernateException { errorIfClosed(); LOG.debug( "Disconnecting session" ); + transactionCoordinator.getJdbcCoordinator().releaseResources(); return transactionCoordinator.getJdbcCoordinator().getLogicalConnection().manualDisconnect(); } @@ -707,6 +710,11 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc if ( persistenceContext.getCascadeLevel() == 0 ) { actionQueue.checkNoUnresolvedActionsAfterOperation(); } + delayedAfterCompletion(); + } + + private void delayedAfterCompletion() { + transactionCoordinator.getSynchronizationCallbackCoordinator().delayedAfterCompletion(); } // saveOrUpdate() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -809,6 +817,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( LockEventListener listener : listeners( EventType.LOCK ) ) { listener.onLock( event ); } + delayedAfterCompletion(); } @@ -833,6 +842,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) { listener.onPersist( event, copiedAlready ); } + delayedAfterCompletion(); } private void firePersist(PersistEvent event) { @@ -868,6 +878,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( PersistEventListener listener : listeners( EventType.PERSIST_ONFLUSH ) ) { listener.onPersist( event, copiedAlready ); } + delayedAfterCompletion(); } private void firePersistOnFlush(PersistEvent event) { @@ -912,6 +923,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( MergeEventListener listener : listeners( EventType.MERGE ) ) { listener.onMerge( event, copiedAlready ); } + delayedAfterCompletion(); } @@ -944,6 +956,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { listener.onDelete( event ); } + delayedAfterCompletion(); } private void fireDelete(DeleteEvent event, Set transientEntities) { @@ -952,6 +965,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { listener.onDelete( event, transientEntities ); } + delayedAfterCompletion(); } @@ -1077,6 +1091,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( LoadEventListener listener : listeners( EventType.LOAD ) ) { listener.onLoad( event, loadType ); } + delayedAfterCompletion(); } private void fireResolveNaturalId(ResolveNaturalIdEvent event) { @@ -1085,6 +1100,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( ResolveNaturalIdEventListener listener : listeners( EventType.RESOLVE_NATURAL_ID ) ) { listener.onResolveNaturalId( event ); } + delayedAfterCompletion(); } @@ -1121,6 +1137,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { listener.onRefresh( event ); } + delayedAfterCompletion(); } private void fireRefresh(Map refreshedAlready, RefreshEvent event) { @@ -1129,6 +1146,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { listener.onRefresh( event, refreshedAlready ); } + delayedAfterCompletion(); } @@ -1149,6 +1167,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( ReplicateEventListener listener : listeners( EventType.REPLICATE ) ) { listener.onReplicate( event ); } + delayedAfterCompletion(); } @@ -1156,7 +1175,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc /** * remove any hard references to the entity that are held by the infrastructure - * (references held by application or other persistant instances are okay) + * (references held by application or other persistent instances are okay) */ public void evict(Object object) throws HibernateException { fireEvict( new EvictEvent( object, this ) ); @@ -1168,6 +1187,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( EvictEventListener listener : listeners( EventType.EVICT ) ) { listener.onEvict( event ); } + delayedAfterCompletion(); } /** @@ -1199,6 +1219,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( DirtyCheckEventListener listener : listeners( EventType.DIRTY_CHECK ) ) { listener.onDirtyCheck( event ); } + delayedAfterCompletion(); return event.isDirty(); } @@ -1212,6 +1233,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( FlushEventListener listener : listeners( EventType.FLUSH ) ) { listener.onFlush( flushEvent ); } + delayedAfterCompletion(); } public void forceFlush(EntityEntry entityEntry) throws HibernateException { @@ -1250,6 +1272,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc finally { dontFlushFromFind--; afterOperation(success); + delayedAfterCompletion(); } return results; } @@ -1269,6 +1292,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc } finally { afterOperation(success); + delayedAfterCompletion(); } return result; } @@ -1290,6 +1314,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc success = true; } finally { afterOperation(success); + delayedAfterCompletion(); } return result; } @@ -1306,6 +1331,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return plan.performIterate( queryParameters, this ); } finally { + delayedAfterCompletion(); dontFlushFromFind--; } } @@ -1320,6 +1346,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return plan.performScroll( queryParameters, this ); } finally { + delayedAfterCompletion(); dontFlushFromFind--; } } @@ -1334,13 +1361,16 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc getFilterQueryPlan( collection, queryString, null, false ).getParameterMetadata() ); filter.setComment( queryString ); + delayedAfterCompletion(); return filter; } public Query getNamedQuery(String queryName) throws MappingException { errorIfClosed(); checkTransactionSynchStatus(); - return super.getNamedQuery( queryName ); + Query query = super.getNamedQuery( queryName ); + delayedAfterCompletion(); + return query; } public Object instantiate(String entityName, Serializable id) throws HibernateException { @@ -1357,6 +1387,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc if ( result == null ) { result = persister.instantiate( id, this ); } + delayedAfterCompletion(); return result; } @@ -1526,6 +1557,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc finally { dontFlushFromFind--; afterOperation(success); + delayedAfterCompletion(); } return results; } @@ -1535,7 +1567,9 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc errorIfClosed(); checkTransactionSynchStatus(); FilterQueryPlan plan = getFilterQueryPlan( collection, filter, queryParameters, true ); - return plan.performIterate( queryParameters, this ); + Iterator itr = plan.performIterate( queryParameters, this ); + delayedAfterCompletion(); + return itr; } public Criteria createCriteria(Class persistentClass, String alias) { @@ -1562,14 +1596,17 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return new CriteriaImpl(entityName, this); } - public ScrollableResults scroll(CriteriaImpl criteria, ScrollMode scrollMode) { + public ScrollableResults scroll(Criteria criteria, ScrollMode scrollMode) { + // TODO: Is this guaranteed to always be CriteriaImpl? + CriteriaImpl criteriaImpl = (CriteriaImpl) criteria; + errorIfClosed(); checkTransactionSynchStatus(); - String entityName = criteria.getEntityOrClassName(); + String entityName = criteriaImpl.getEntityOrClassName(); CriteriaLoader loader = new CriteriaLoader( getOuterJoinLoadable(entityName), factory, - criteria, + criteriaImpl, entityName, getLoadQueryInfluencers() ); @@ -1579,12 +1616,16 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return loader.scroll(this, scrollMode); } finally { + delayedAfterCompletion(); dontFlushFromFind--; } } - public List list(CriteriaImpl criteria) throws HibernateException { - final NaturalIdLoadAccess naturalIdLoadAccess = this.tryNaturalIdLoadAccess( criteria ); + public List list(Criteria criteria) throws HibernateException { + // TODO: Is this guaranteed to always be CriteriaImpl? + CriteriaImpl criteriaImpl = (CriteriaImpl) criteria; + + final NaturalIdLoadAccess naturalIdLoadAccess = this.tryNaturalIdLoadAccess( criteriaImpl ); if ( naturalIdLoadAccess != null ) { // EARLY EXIT! return Arrays.asList( naturalIdLoadAccess.load() ); @@ -1592,7 +1633,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc errorIfClosed(); checkTransactionSynchStatus(); - String[] implementors = factory.getImplementors( criteria.getEntityOrClassName() ); + String[] implementors = factory.getImplementors( criteriaImpl.getEntityOrClassName() ); int size = implementors.length; CriteriaLoader[] loaders = new CriteriaLoader[size]; @@ -1602,7 +1643,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc loaders[i] = new CriteriaLoader( getOuterJoinLoadable( implementors[i] ), factory, - criteria, + criteriaImpl, implementors[i], getLoadQueryInfluencers() ); @@ -1627,6 +1668,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc finally { dontFlushFromFind--; afterOperation(success); + delayedAfterCompletion(); } return results; @@ -1726,6 +1768,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc // an entry in the session's persistence context and the entry reports // that the entity has not been removed EntityEntry entry = persistenceContext.getEntry( object ); + delayedAfterCompletion(); return entry != null && entry.getStatus() != Status.DELETED && entry.getStatus() != Status.GONE; } @@ -1742,21 +1785,21 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc } @Override - public StoredProcedureCall createStoredProcedureCall(String procedureName) { + public ProcedureCall createStoredProcedureCall(String procedureName) { errorIfClosed(); checkTransactionSynchStatus(); return super.createStoredProcedureCall( procedureName ); } @Override - public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { + public ProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { errorIfClosed(); checkTransactionSynchStatus(); return super.createStoredProcedureCall( procedureName, resultSetMappings ); } @Override - public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { + public ProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { errorIfClosed(); checkTransactionSynchStatus(); return super.createStoredProcedureCall( procedureName, resultClasses ); @@ -1780,6 +1823,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return loader.scroll(queryParameters, this); } finally { + delayedAfterCompletion(); dontFlushFromFind--; } } @@ -1807,6 +1851,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc } finally { dontFlushFromFind--; + delayedAfterCompletion(); afterOperation(success); } } @@ -1824,6 +1869,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc for ( InitializeCollectionEventListener listener : listeners( EventType.INIT_COLLECTION ) ) { listener.onInitializeCollection( event ); } + delayedAfterCompletion(); } public String bestGuessEntityName(Object object) { @@ -2079,8 +2125,12 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc loadQueryInfluencers.disableFetchProfile( name ); } - private void checkTransactionSynchStatus() { + pulseTransactionCoordinator(); + delayedAfterCompletion(); + } + + private void pulseTransactionCoordinator() { if ( !isClosed() ) { transactionCoordinator.pulse(); } @@ -2134,7 +2184,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc * @throws IOException Indicates a general IO stream exception */ private void writeObject(ObjectOutputStream oos) throws IOException { - if ( ! transactionCoordinator.getJdbcCoordinator().getLogicalConnection().isReadyForSerialization() ) { + if ( ! transactionCoordinator.getJdbcCoordinator().isReadyForSerialization() ) { throw new IllegalStateException( "Cannot serialize a session while connected" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index f1f8abcdcf..2ee8dfca9f 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -30,8 +30,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import org.jboss.logging.Logger; - import org.hibernate.CacheMode; import org.hibernate.ConnectionReleaseMode; import org.hibernate.Criteria; @@ -73,6 +71,7 @@ import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.Type; +import org.jboss.logging.Logger; /** * @author Gavin King @@ -405,7 +404,7 @@ public class StatelessSessionImpl extends AbstractSessionImpl implements Statele @Override public Connection connection() { errorIfClosed(); - return transactionCoordinator.getJdbcCoordinator().getLogicalConnection().getDistinctConnectionProxy(); + return transactionCoordinator.getJdbcCoordinator().getLogicalConnection().getConnection(); } @Override @@ -618,13 +617,16 @@ public class StatelessSessionImpl extends AbstractSessionImpl implements Statele } @Override - public ScrollableResults scroll(CriteriaImpl criteria, ScrollMode scrollMode) { + public ScrollableResults scroll(Criteria criteria, ScrollMode scrollMode) { + // TODO: Is this guaranteed to always be CriteriaImpl? + CriteriaImpl criteriaImpl = (CriteriaImpl) criteria; + errorIfClosed(); - String entityName = criteria.getEntityOrClassName(); + String entityName = criteriaImpl.getEntityOrClassName(); CriteriaLoader loader = new CriteriaLoader( getOuterJoinLoadable( entityName ), factory, - criteria, + criteriaImpl, entityName, getLoadQueryInfluencers() ); @@ -633,9 +635,12 @@ public class StatelessSessionImpl extends AbstractSessionImpl implements Statele @Override @SuppressWarnings( {"unchecked"}) - public List list(CriteriaImpl criteria) throws HibernateException { + public List list(Criteria criteria) throws HibernateException { + // TODO: Is this guaranteed to always be CriteriaImpl? + CriteriaImpl criteriaImpl = (CriteriaImpl) criteria; + errorIfClosed(); - String[] implementors = factory.getImplementors( criteria.getEntityOrClassName() ); + String[] implementors = factory.getImplementors( criteriaImpl.getEntityOrClassName() ); int size = implementors.length; CriteriaLoader[] loaders = new CriteriaLoader[size]; @@ -643,7 +648,7 @@ public class StatelessSessionImpl extends AbstractSessionImpl implements Statele loaders[i] = new CriteriaLoader( getOuterJoinLoadable( implementors[i] ), factory, - criteria, + criteriaImpl, implementors[i], getLoadQueryInfluencers() ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureCallImpl.java deleted file mode 100644 index b477df95b5..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureCallImpl.java +++ /dev/null @@ -1,609 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2012, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.internal; - -import java.sql.CallableStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.persistence.ParameterMode; -import javax.persistence.TemporalType; - -import org.jboss.logging.Logger; - -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.MappingException; -import org.hibernate.QueryException; -import org.hibernate.StoredProcedureCall; -import org.hibernate.StoredProcedureOutputs; -import org.hibernate.cfg.NotYetImplementedException; -import org.hibernate.engine.ResultSetMappingDefinition; -import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; -import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn; -import org.hibernate.engine.spi.QueryParameters; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; -import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.type.DateType; -import org.hibernate.type.ProcedureParameterExtractionAware; -import org.hibernate.type.Type; - -/** - * @author Steve Ebersole - */ -public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl implements StoredProcedureCall { - private static final Logger log = Logger.getLogger( StoredProcedureCallImpl.class ); - - private final String procedureName; - private final NativeSQLQueryReturn[] queryReturns; - - private TypeOfParameter typeOfParameters = TypeOfParameter.UNKNOWN; - private List registeredParameters = new ArrayList(); - - private Set synchronizedQuerySpaces; - - @SuppressWarnings("unchecked") - public StoredProcedureCallImpl(SessionImplementor session, String procedureName) { - this( session, procedureName, (List) null ); - } - - public StoredProcedureCallImpl(SessionImplementor session, String procedureName, List queryReturns) { - super( session ); - this.procedureName = procedureName; - - if ( queryReturns == null || queryReturns.isEmpty() ) { - this.queryReturns = new NativeSQLQueryReturn[0]; - } - else { - this.queryReturns = queryReturns.toArray( new NativeSQLQueryReturn[ queryReturns.size() ] ); - } - } - - public StoredProcedureCallImpl(SessionImplementor session, String procedureName, Class... resultClasses) { - this( session, procedureName, collectQueryReturns( resultClasses ) ); - } - - private static List collectQueryReturns(Class[] resultClasses) { - if ( resultClasses == null || resultClasses.length == 0 ) { - return null; - } - - List queryReturns = new ArrayList( resultClasses.length ); - int i = 1; - for ( Class resultClass : resultClasses ) { - queryReturns.add( new NativeSQLQueryRootReturn( "alias" + i, resultClass.getName(), LockMode.READ ) ); - i++; - } - return queryReturns; - } - - public StoredProcedureCallImpl(SessionImplementor session, String procedureName, String... resultSetMappings) { - this( session, procedureName, collectQueryReturns( session, resultSetMappings ) ); - } - - private static List collectQueryReturns(SessionImplementor session, String[] resultSetMappings) { - if ( resultSetMappings == null || resultSetMappings.length == 0 ) { - return null; - } - - List queryReturns = new ArrayList( resultSetMappings.length ); - for ( String resultSetMapping : resultSetMappings ) { - ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( resultSetMapping ); - if ( mapping == null ) { - throw new MappingException( "Unknown SqlResultSetMapping [" + resultSetMapping + "]" ); - } - queryReturns.addAll( Arrays.asList( mapping.getQueryReturns() ) ); - } - return queryReturns; - } - -// public StoredProcedureCallImpl( -// SessionImplementor session, -// String procedureName, -// List parameters) { -// // this form is intended for named stored procedure calls. -// // todo : introduce a NamedProcedureCallDefinition object to hold all needed info and pass that in here; will help with EM.addNamedQuery as well.. -// this( session, procedureName ); -// for ( StoredProcedureParameter parameter : parameters ) { -// registerParameter( (StoredProcedureParameterImplementor) parameter ); -// } -// } - - @Override - public String getProcedureName() { - return procedureName; - } - - NativeSQLQueryReturn[] getQueryReturns() { - return queryReturns; - } - - @Override - @SuppressWarnings("unchecked") - public StoredProcedureCall registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { - registerParameter( new PositionalStoredProcedureParameter( this, position, mode, type ) ); - return this; - } - - private void registerParameter(StoredProcedureParameterImplementor parameter) { - if ( StringHelper.isNotEmpty( parameter.getName() ) ) { - prepareForNamedParameters(); - } - else if ( parameter.getPosition() != null ) { - prepareForPositionalParameters(); - } - else { - throw new IllegalArgumentException( "Given parameter did not define name nor position [" + parameter + "]" ); - } - registeredParameters.add( parameter ); - } - - private void prepareForPositionalParameters() { - if ( typeOfParameters == TypeOfParameter.NAMED ) { - throw new QueryException( "Cannot mix named and positional parameters" ); - } - typeOfParameters = TypeOfParameter.POSITIONAL; - } - - private void prepareForNamedParameters() { - if ( typeOfParameters == TypeOfParameter.POSITIONAL ) { - throw new QueryException( "Cannot mix named and positional parameters" ); - } - if ( typeOfParameters == null ) { - // protect to only do this check o - // nce - final ExtractedDatabaseMetaData databaseMetaData = session().getTransactionCoordinator() - .getJdbcCoordinator() - .getLogicalConnection() - .getJdbcServices() - .getExtractedMetaDataSupport(); - if ( ! databaseMetaData.supportsNamedParameters() ) { - throw new QueryException( - "Named stored procedure parameters used, but JDBC driver does not support named parameters" - ); - } - typeOfParameters = TypeOfParameter.NAMED; - } - } - - @Override - @SuppressWarnings("unchecked") - public StoredProcedureCall registerStoredProcedureParameter(String name, Class type, ParameterMode mode) { - registerParameter( new NamedStoredProcedureParameter( this, name, mode, type ) ); - return this; - } - - @Override - @SuppressWarnings("unchecked") - public List getRegisteredParameters() { - return new ArrayList( registeredParameters ); - } - - @Override - public StoredProcedureParameterImplementor getRegisteredParameter(String name) { - if ( typeOfParameters != TypeOfParameter.NAMED ) { - throw new IllegalArgumentException( "Names were not used to register parameters with this stored procedure call" ); - } - for ( StoredProcedureParameterImplementor parameter : registeredParameters ) { - if ( name.equals( parameter.getName() ) ) { - return parameter; - } - } - throw new IllegalArgumentException( "Could not locate parameter registered under that name [" + name + "]" ); - } - - @Override - public StoredProcedureParameterImplementor getRegisteredParameter(int position) { - try { - return registeredParameters.get( position ); - } - catch ( Exception e ) { - throw new QueryException( "Could not locate parameter registered using that position [" + position + "]" ); - } - } - - @Override - public StoredProcedureOutputs getOutputs() { - - // todo : going to need a very specialized Loader for this. - // or, might be a good time to look at splitting Loader up into: - // 1) building statement objects - // 2) executing statement objects - // 3) processing result sets - - // for now assume there are no resultClasses nor mappings defined.. - // TOTAL PROOF-OF-CONCEPT!!!!!! - - final StringBuilder buffer = new StringBuilder().append( "{call " ) - .append( procedureName ) - .append( "(" ); - String sep = ""; - for ( StoredProcedureParameterImplementor parameter : registeredParameters ) { - for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) { - buffer.append( sep ).append( "?" ); - sep = ","; - } - } - buffer.append( ")}" ); - - try { - final CallableStatement statement = session().getTransactionCoordinator() - .getJdbcCoordinator() - .getLogicalConnection() - .getShareableConnectionProxy() - .prepareCall( buffer.toString() ); - - // prepare parameters - int i = 1; - for ( StoredProcedureParameterImplementor parameter : registeredParameters ) { - if ( parameter == null ) { - throw new QueryException( "Registered stored procedure parameters had gaps" ); - } - - parameter.prepare( statement, i ); - i += parameter.getSqlTypes().length; - } - - return new StoredProcedureOutputsImpl( this, statement ); - } - catch (SQLException e) { - throw session().getFactory().getSQLExceptionHelper().convert( - e, - "Error preparing CallableStatement", - getProcedureName() - ); - } - } - - - @Override - public Type[] getReturnTypes() throws HibernateException { - throw new NotYetImplementedException(); - } - - protected Set synchronizedQuerySpaces() { - if ( synchronizedQuerySpaces == null ) { - synchronizedQuerySpaces = new HashSet(); - } - return synchronizedQuerySpaces; - } - - @Override - @SuppressWarnings("unchecked") - public Collection getSynchronizedQuerySpaces() { - if ( synchronizedQuerySpaces == null ) { - return Collections.emptySet(); - } - else { - return Collections.unmodifiableSet( synchronizedQuerySpaces ); - } - } - - public Set getSynchronizedQuerySpacesSet() { - return (Set) getSynchronizedQuerySpaces(); - } - - @Override - public StoredProcedureCallImpl addSynchronizedQuerySpace(String querySpace) { - synchronizedQuerySpaces().add( querySpace ); - return this; - } - - @Override - public StoredProcedureCallImpl addSynchronizedEntityName(String entityName) { - addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityName ) ); - return this; - } - - protected void addSynchronizedQuerySpaces(EntityPersister persister) { - synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) ); - } - - @Override - public StoredProcedureCallImpl addSynchronizedEntityClass(Class entityClass) { - addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityClass.getName() ) ); - return this; - } - - public QueryParameters buildQueryParametersObject() { - QueryParameters qp = super.buildQueryParametersObject(); - // both of these are for documentation purposes, they are actually handled directly... - qp.setAutoDiscoverScalarTypes( true ); - qp.setCallable( true ); - return qp; - } - - public StoredProcedureParameterImplementor[] collectRefCursorParameters() { - List refCursorParams = new ArrayList(); - for ( StoredProcedureParameterImplementor param : registeredParameters ) { - if ( param.getMode() == ParameterMode.REF_CURSOR ) { - refCursorParams.add( param ); - } - } - return refCursorParams.toArray( new StoredProcedureParameterImplementor[refCursorParams.size()] ); - } - - /** - * Ternary logic enum - */ - private static enum TypeOfParameter { - NAMED, - POSITIONAL, - UNKNOWN - } - - protected static interface StoredProcedureParameterImplementor extends StoredProcedureParameter { - public void prepare(CallableStatement statement, int i) throws SQLException; - - public int[] getSqlTypes(); - - public T extract(CallableStatement statement); - } - - public static abstract class AbstractStoredProcedureParameterImpl implements StoredProcedureParameterImplementor { - private final StoredProcedureCallImpl procedureCall; - - private final ParameterMode mode; - private final Class type; - - private int startIndex; - private Type hibernateType; - private int[] sqlTypes; - - private StoredProcedureParameterBindImpl bind; - - protected AbstractStoredProcedureParameterImpl( - StoredProcedureCallImpl procedureCall, - ParameterMode mode, - Class type) { - this.procedureCall = procedureCall; - this.mode = mode; - this.type = type; - - setHibernateType( session().getFactory().getTypeResolver().heuristicType( type.getName() ) ); - } - - @Override - public String getName() { - return null; - } - - @Override - public Integer getPosition() { - return null; - } - - @Override - public Class getType() { - return type; - } - - @Override - public ParameterMode getMode() { - return mode; - } - - @Override - public void setHibernateType(Type type) { - if ( type == null ) { - throw new IllegalArgumentException( "Type cannot be null" ); - } - this.hibernateType = type; - this.sqlTypes = hibernateType.sqlTypes( session().getFactory() ); - } - - protected SessionImplementor session() { - return procedureCall.session(); - } - - @Override - public void prepare(CallableStatement statement, int startIndex) throws SQLException { - if ( mode == ParameterMode.REF_CURSOR ) { - throw new NotYetImplementedException( "Support for REF_CURSOR parameters not yet supported" ); - } - - this.startIndex = startIndex; - if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - if ( sqlTypes.length > 1 ) { - if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) - && ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction() ) { - // the type can handle multi-param extraction... - } - else { - // it cannot... - throw new UnsupportedOperationException( - "Type [" + hibernateType + "] does support multi-parameter value extraction" - ); - } - } - for ( int i = 0; i < sqlTypes.length; i++ ) { - statement.registerOutParameter( startIndex + i, sqlTypes[i] ); - } - } - - if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { - if ( bind == null || bind.getValue() == null ) { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound; assuming procedure defines default value", - procedureCall.getProcedureName(), - this - ); - } - else { - final Type typeToUse; - if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.TIMESTAMP ) { - typeToUse = hibernateType; - } - else if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.DATE ) { - typeToUse = DateType.INSTANCE; - } - else { - typeToUse = hibernateType; - } - typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() ); - } - } - } - else { - // we have a REF_CURSOR type param - if ( procedureCall.typeOfParameters == TypeOfParameter.NAMED ) { - session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, getName() ); - } - else { - session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, getPosition() ); - } - } - } - - public int[] getSqlTypes() { - return sqlTypes; - } - - @Override - public StoredProcedureParameterBind getParameterBind() { - return bind; - } - - @Override - public void bindValue(T value) { - this.bind = new StoredProcedureParameterBindImpl( value ); - } - - @Override - public void bindValue(T value, TemporalType explicitTemporalType) { - if ( explicitTemporalType != null ) { - if ( ! isDateTimeType() ) { - throw new IllegalArgumentException( "TemporalType should not be specified for non date/time type" ); - } - } - this.bind = new StoredProcedureParameterBindImpl( value, explicitTemporalType ); - } - - private boolean isDateTimeType() { - return Date.class.isAssignableFrom( type ) - || Calendar.class.isAssignableFrom( type ); - } - - @Override - @SuppressWarnings("unchecked") - public T extract(CallableStatement statement) { - if ( mode == ParameterMode.IN ) { - throw new QueryException( "IN parameter not valid for output extraction" ); - } - else if ( mode == ParameterMode.REF_CURSOR ) { - throw new QueryException( "REF_CURSOR parameters should be accessed via results" ); - } - - try { - if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { - return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( statement, startIndex, session() ); - } - else { - return (T) statement.getObject( startIndex ); - } - } - catch (SQLException e) { - throw procedureCall.session().getFactory().getSQLExceptionHelper().convert( - e, - "Unable to extract OUT/INOUT parameter value" - ); - } - } - } - - public static class StoredProcedureParameterBindImpl implements StoredProcedureParameterBind { - private final T value; - private final TemporalType explicitTemporalType; - - public StoredProcedureParameterBindImpl(T value) { - this( value, null ); - } - - public StoredProcedureParameterBindImpl(T value, TemporalType explicitTemporalType) { - this.value = value; - this.explicitTemporalType = explicitTemporalType; - } - - @Override - public T getValue() { - return value; - } - - @Override - public TemporalType getExplicitTemporalType() { - return explicitTemporalType; - } - } - - public static class NamedStoredProcedureParameter extends AbstractStoredProcedureParameterImpl { - private final String name; - - public NamedStoredProcedureParameter( - StoredProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type) { - super( procedureCall, mode, type ); - this.name = name; - } - - @Override - public String getName() { - return name; - } - } - - public static class PositionalStoredProcedureParameter extends AbstractStoredProcedureParameterImpl { - private final Integer position; - - public PositionalStoredProcedureParameter( - StoredProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type) { - super( procedureCall, mode, type ); - this.position = position; - } - - @Override - public Integer getPosition() { - return position; - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ClassLoaderHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ClassLoaderHelper.java new file mode 100644 index 0000000000..78e2f85826 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ClassLoaderHelper.java @@ -0,0 +1,38 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.internal.util; + +/** + * This exists purely to allow custom ClassLoaders to be injected and used + * prior to ServiceRegistry and ClassLoadingService existence. This should be + * replaced in Hibernate 5. + * + * @author Brett Meyer + */ +public class ClassLoaderHelper { + + public static ClassLoader overridenClassLoader = null; + + public static ClassLoader getContextClassLoader() { + return overridenClassLoader != null ? + overridenClassLoader : Thread.currentThread().getContextClassLoader(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java index e43aea33b0..3cd79bdb99 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ConfigHelper.java @@ -78,7 +78,7 @@ public final class ConfigHelper { // First, try to locate this resource through the current // context classloader. - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader contextClassLoader = ClassLoaderHelper.getContextClassLoader(); if (contextClassLoader!=null) { url = contextClassLoader.getResource(path); } @@ -159,7 +159,7 @@ public final class ConfigHelper { resource.substring(1) : resource; InputStream stream = null; - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = ClassLoaderHelper.getContextClassLoader(); if (classLoader!=null) { stream = classLoader.getResourceAsStream( stripped ); } @@ -182,7 +182,7 @@ public final class ConfigHelper { InputStream stream = null; - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = ClassLoaderHelper.getContextClassLoader(); if ( classLoader != null ) { stream = classLoader.getResourceAsStream( resource ); if ( stream == null && hasLeadingSlash ) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/LockModeConverter.java b/hibernate-core/src/main/java/org/hibernate/internal/util/LockModeConverter.java index 3168734251..fb4362a0ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/LockModeConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/LockModeConverter.java @@ -57,7 +57,8 @@ public class LockModeConverter { } else if ( lockMode == LockMode.PESSIMISTIC_WRITE || lockMode == LockMode.UPGRADE - || lockMode == LockMode.UPGRADE_NOWAIT ) { + || lockMode == LockMode.UPGRADE_NOWAIT + || lockMode == LockMode.UPGRADE_SKIPLOCKED) { return LockModeType.PESSIMISTIC_WRITE; } else if ( lockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index fda9646557..749f831ba3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -166,9 +166,9 @@ public final class ReflectHelper { */ public static Class classForName(String name, Class caller) throws ClassNotFoundException { try { - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - if ( contextClassLoader != null ) { - return contextClassLoader.loadClass( name ); + ClassLoader classLoader = ClassLoaderHelper.getContextClassLoader(); + if ( classLoader != null ) { + return classLoader.loadClass( name ); } } catch ( Throwable ignore ) { @@ -192,7 +192,15 @@ public final class ReflectHelper { * @throws ClassNotFoundException From {@link Class#forName(String)}. */ public static Class classForName(String name) throws ClassNotFoundException { - return classForName( name, null ); + try { + ClassLoader classLoader = ClassLoaderHelper.getContextClassLoader(); + if ( classLoader != null ) { + return classLoader.loadClass(name); + } + } + catch ( Throwable ignore ) { + } + return Class.forName( name ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/SerializationHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/SerializationHelper.java index d7c1013077..a8d89e9b08 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/SerializationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/SerializationHelper.java @@ -191,7 +191,7 @@ public final class SerializationHelper { * @return The current TCCL */ public static ClassLoader defaultClassLoader() { - return Thread.currentThread().getContextClassLoader(); + return ClassLoaderHelper.getContextClassLoader(); } public static ClassLoader hibernateClassLoader() { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index 4cd2e5eb47..216ac9b199 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -752,4 +752,15 @@ public final class StringHelper { } } } + + /** + * Takes a String s and returns a new String[1] with s as the only element. + * If s is null or "", return String[0]. + * + * @param s + * @return String[] + */ + public static String[] toArrayElement(String s) { + return ( s == null || s.length() == 0 ) ? new String[0] : new String[] { s }; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java index 7a02ac2bed..6ab6fd01d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java @@ -192,7 +192,7 @@ public final class ArrayHelper { int[] arr = new int[ coll.size() ]; int i=0; while( iter.hasNext() ) { - arr[i++] = ( (Integer) iter.next() ).intValue(); + arr[i++] = (Integer) iter.next(); } return arr; } @@ -202,7 +202,7 @@ public final class ArrayHelper { boolean[] arr = new boolean[ coll.size() ]; int i=0; while( iter.hasNext() ) { - arr[i++] = ( (Boolean) iter.next() ).booleanValue(); + arr[i++] = (Boolean) iter.next(); } return arr; } @@ -313,23 +313,29 @@ public final class ArrayHelper { } public static boolean isAllNegative(int[] array) { - for ( int i=0; i=0 ) return false; + for ( int anArray : array ) { + if ( anArray >= 0 ) { + return false; + } } return true; } public static boolean isAllTrue(boolean[] array) { - for ( int i=0; i= maxFetchDepth.intValue(); + return maxFetchDepth!=null && currentDepth >= maxFetchDepth; } protected boolean isTooManyCollections() { @@ -841,7 +841,7 @@ public class JoinWalker { } Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth(); - final boolean tooDeep = maxFetchDepth!=null && depth >= maxFetchDepth.intValue(); + final boolean tooDeep = maxFetchDepth!=null && depth >= maxFetchDepth; return !tooDeep && !isDuplicateAssociation(lhsTable, lhsColumnNames, type); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index 66a618930d..b935327103 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -254,22 +254,24 @@ public abstract class Loader { Dialect dialect, List afterLoadActions) { if ( dialect.useFollowOnLocking() ) { - LOG.usingFollowOnLocking(); // currently only one lock mode is allowed in follow-on locking final LockMode lockMode = determineFollowOnLockMode( parameters.getLockOptions() ); final LockOptions lockOptions = new LockOptions( lockMode ); - lockOptions.setTimeOut( parameters.getLockOptions().getTimeOut() ); - lockOptions.setScope( parameters.getLockOptions().getScope() ); - afterLoadActions.add( - new AfterLoadAction() { - @Override - public void afterLoad(SessionImplementor session, Object entity, Loadable persister) { - ( (Session) session ).buildLockRequest( lockOptions ).lock( persister.getEntityName(), entity ); + if ( lockOptions.getLockMode() != LockMode.UPGRADE_SKIPLOCKED ) { + LOG.usingFollowOnLocking(); + lockOptions.setTimeOut( parameters.getLockOptions().getTimeOut() ); + lockOptions.setScope( parameters.getLockOptions().getScope() ); + afterLoadActions.add( + new AfterLoadAction() { + @Override + public void afterLoad(SessionImplementor session, Object entity, Loadable persister) { + ( (Session) session ).buildLockRequest( lockOptions ).lock( persister.getEntityName(), entity ); + } } - } - ); - parameters.setLockOptions( new LockOptions() ); - return true; + ); + parameters.setLockOptions( new LockOptions() ); + return true; + } } return false; } @@ -906,7 +908,7 @@ public abstract class Loader { return processResultSet( rs, queryParameters, session, returnProxies, forcedResultTransformer, maxRows, afterLoadActions ); } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } @@ -1904,11 +1906,11 @@ public abstract class Loader { LOG.tracev( "Bound [{0}] parameters total", col ); } catch ( SQLException sqle ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); throw sqle; } catch ( HibernateException he ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); throw he; } @@ -2029,7 +2031,7 @@ public abstract class Loader { throws SQLException, HibernateException { try { - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); rs = wrapResultSetIfEnabled( rs , session ); if ( !limitHandler.supportsLimitOffset() || !LimitHelper.useLimit( limitHandler, selection ) ) { @@ -2042,7 +2044,7 @@ public abstract class Loader { return rs; } catch ( SQLException sqle ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); throw sqle; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/collection/DynamicBatchingCollectionInitializerBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/collection/DynamicBatchingCollectionInitializerBuilder.java index d17125b896..cce8cb8f43 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/collection/DynamicBatchingCollectionInitializerBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/collection/DynamicBatchingCollectionInitializerBuilder.java @@ -259,7 +259,7 @@ public class DynamicBatchingCollectionInitializerBuilder extends BatchingCollect processResultSet( rs, queryParameters, session, true, null, maxRows, afterLoadActions ); } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/ComponentCollectionCriteriaInfoProvider.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/ComponentCollectionCriteriaInfoProvider.java index 84c461210f..2f86b55431 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/ComponentCollectionCriteriaInfoProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/ComponentCollectionCriteriaInfoProvider.java @@ -66,7 +66,7 @@ class ComponentCollectionCriteriaInfoProvider implements CriteriaInfoProvider { } public PropertyMapping getPropertyMapping() { - return (PropertyMapping)persister; + return persister; } public Type getType(String relativePath) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java index 521ffac762..e1caffb218 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaLoader.java @@ -205,27 +205,28 @@ public class CriteriaLoader extends OuterJoinLoader { } if ( dialect.useFollowOnLocking() ) { - // Dialect prefers to perform locking in a separate step - LOG.usingFollowOnLocking(); + final LockMode lockMode = determineFollowOnLockMode( lockOptions ); + if( lockMode != LockMode.UPGRADE_SKIPLOCKED ) { + // Dialect prefers to perform locking in a separate step + LOG.usingFollowOnLocking(); - final LockMode lockMode = determineFollowOnLockMode( lockOptions ); - final LockOptions lockOptionsToUse = new LockOptions( lockMode ); - lockOptionsToUse.setTimeOut( lockOptions.getTimeOut() ); - lockOptionsToUse.setScope( lockOptions.getScope() ); + final LockOptions lockOptionsToUse = new LockOptions( lockMode ); + lockOptionsToUse.setTimeOut( lockOptions.getTimeOut() ); + lockOptionsToUse.setScope( lockOptions.getScope() ); - afterLoadActions.add( - new AfterLoadAction() { - @Override - public void afterLoad(SessionImplementor session, Object entity, Loadable persister) { - ( (Session) session ).buildLockRequest( lockOptionsToUse ) - .lock( persister.getEntityName(), entity ); - } - } - ); - parameters.setLockOptions( new LockOptions() ); - return sql; + afterLoadActions.add( + new AfterLoadAction() { + @Override + public void afterLoad(SessionImplementor session, Object entity, Loadable persister) { + ( (Session) session ).buildLockRequest( lockOptionsToUse ) + .lock( persister.getEntityName(), entity ); + } + } + ); + parameters.setLockOptions( new LockOptions() ); + return sql; + } } - final LockOptions locks = new LockOptions(lockOptions.getLockMode()); locks.setScope( lockOptions.getScope()); locks.setTimeOut( lockOptions.getTimeOut()); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/EntityCriteriaInfoProvider.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/EntityCriteriaInfoProvider.java index 04169b661f..5eb354844a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/EntityCriteriaInfoProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/EntityCriteriaInfoProvider.java @@ -50,7 +50,7 @@ class EntityCriteriaInfoProvider implements CriteriaInfoProvider { } public PropertyMapping getPropertyMapping() { - return (PropertyMapping)persister; + return persister; } public Type getType(String relativePath) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java index f8b1faed12..24527e0486 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java @@ -261,7 +261,7 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions ); } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index 569b08e30d..2c0dccc331 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -84,7 +84,7 @@ public class Column implements Selectable, Serializable, Cloneable { } public void setName(String name) { if ( - name.charAt(0)=='`' || + StringHelper.isNotEmpty( name ) && Dialect.QUOTE.indexOf( name.charAt(0) ) > -1 //TODO: deprecated, remove eventually ) { quoted=true; @@ -108,33 +108,32 @@ public class Column implements Selectable, Serializable, Cloneable { name; } - /** - * For any column name, generate an alias that is unique - * to that column name, and also 10 characters or less - * in length. - */ + @Override public String getAlias(Dialect dialect) { + final int lastLetter = StringHelper.lastIndexOfLetter( name ); + String suffix = Integer.toString(uniqueInteger) + '_'; + String alias = name; - String unique = Integer.toString(uniqueInteger) + '_'; - int lastLetter = StringHelper.lastIndexOfLetter(name); if ( lastLetter == -1 ) { alias = "column"; } - else if ( lastLetter < name.length()-1 ) { - alias = name.substring(0, lastLetter+1); + else if ( name.length() > lastLetter + 1 ) { + alias = name.substring( 0, lastLetter + 1 ); } - if ( alias.length() > dialect.getMaxAliasLength() ) { - alias = alias.substring( 0, dialect.getMaxAliasLength() - unique.length() ); - } - boolean useRawName = name.equals(alias) && - !quoted && - !name.toLowerCase().equals("rowid"); - if ( useRawName ) { - return alias; - } - else { - return alias + unique; + + boolean useRawName = name.length() + suffix.length() <= dialect.getMaxAliasLength() + && !quoted && !name.toLowerCase().equals( "rowid" ); + if ( !useRawName ) { + if ( suffix.length() >= dialect.getMaxAliasLength() ) { + throw new MappingException( String.format( + "Unique suffix [%s] length must be less than maximum [%d]", + suffix, dialect.getMaxAliasLength() ) ); + } + if ( alias.length() + suffix.length() > dialect.getMaxAliasLength() ) { + alias = alias.substring( 0, dialect.getMaxAliasLength() - suffix.length() ); + } } + return alias + suffix; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java index 9fff9398e7..be846eaf46 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -38,7 +38,7 @@ import org.hibernate.engine.spi.Mapping; public abstract class Constraint implements RelationalModel, Serializable { private String name; - private final List columns = new ArrayList(); + private final List columns = new ArrayList(); private Table table; public String getName() { @@ -49,10 +49,6 @@ public abstract class Constraint implements RelationalModel, Serializable { this.name = name; } - public Iterator getColumnIterator() { - return columns.iterator(); - } - public void addColumn(Column column) { if ( !columns.contains( column ) ) columns.add( column ); } @@ -77,10 +73,14 @@ public abstract class Constraint implements RelationalModel, Serializable { } public Column getColumn(int i) { - return (Column) columns.get( i ); + return columns.get( i ); + } + //todo duplicated method, remove one + public Iterator getColumnIterator() { + return columns.iterator(); } - public Iterator columnIterator() { + public Iterator columnIterator() { return columns.iterator(); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java index da70576a05..85df62150e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java @@ -24,6 +24,8 @@ package org.hibernate.mapping; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -40,7 +42,8 @@ import org.hibernate.internal.util.StringHelper; public class Index implements RelationalModel, Serializable { private Table table; - private List columns = new ArrayList(); + private List columns = new ArrayList(); + private java.util.Map columnOrderMap = new HashMap( ); private String name; public String sqlCreateString(Dialect dialect, Mapping mapping, String defaultCatalog, String defaultSchema) @@ -50,6 +53,7 @@ public class Index implements RelationalModel, Serializable { getName(), getTable(), getColumnIterator(), + columnOrderMap, false, defaultCatalog, defaultSchema @@ -74,7 +78,8 @@ public class Index implements RelationalModel, Serializable { Dialect dialect, String name, Table table, - Iterator columns, + Iterator columns, + java.util.Map columnOrderMap, boolean unique, String defaultCatalog, String defaultSchema @@ -90,15 +95,30 @@ public class Index implements RelationalModel, Serializable { .append( " on " ) .append( table.getQualifiedName( dialect, defaultCatalog, defaultSchema ) ) .append( " (" ); - Iterator iter = columns; - while ( iter.hasNext() ) { - buf.append( ( (Column) iter.next() ).getQuotedName( dialect ) ); - if ( iter.hasNext() ) buf.append( ", " ); + while ( columns.hasNext() ) { + Column column = columns.next(); + buf.append( column.getQuotedName( dialect ) ); + if ( columnOrderMap.containsKey( column ) ) { + buf.append( " " ).append( columnOrderMap.get( column ) ); + } + if ( columns.hasNext() ) buf.append( ", " ); } buf.append( ")" ); return buf.toString(); } + public static String buildSqlCreateIndexString( + Dialect dialect, + String name, + Table table, + Iterator columns, + boolean unique, + String defaultCatalog, + String defaultSchema + ) { + return buildSqlCreateIndexString( dialect, name, table, columns, Collections.EMPTY_MAP, unique, defaultCatalog, defaultSchema ); + } + // Used only in Table for sqlCreateString (but commented out at the moment) public String sqlConstraintString(Dialect dialect) { @@ -131,7 +151,7 @@ public class Index implements RelationalModel, Serializable { return columns.size(); } - public Iterator getColumnIterator() { + public Iterator getColumnIterator() { return columns.iterator(); } @@ -139,6 +159,13 @@ public class Index implements RelationalModel, Serializable { if ( !columns.contains( column ) ) columns.add( column ); } + public void addColumn(Column column, String order) { + addColumn( column ); + if ( StringHelper.isNotEmpty( order ) ) { + columnOrderMap.put( column, order ); + } + } + public void addColumns(Iterator extraColumns) { while ( extraColumns.hasNext() ) addColumn( (Column) extraColumns.next() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index 0d21a4c04a..ee638cde90 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -55,7 +55,7 @@ public class Table implements RelationalModel, Serializable { private Map columns = new LinkedHashMap(); private KeyValue idValue; private PrimaryKey primaryKey; - private Map indexes = new LinkedHashMap(); + private Map indexes = new LinkedHashMap(); private Map foreignKeys = new LinkedHashMap(); private Map uniqueKeys = new LinkedHashMap(); private int uniqueInteger; @@ -214,7 +214,7 @@ public class Table implements RelationalModel, Serializable { } public void addColumn(Column column) { - Column old = (Column) getColumn( column ); + Column old = getColumn( column ); if ( old == null ) { columns.put( column.getCanonicalName(), column ); column.uniqueInteger = columns.size(); @@ -232,7 +232,7 @@ public class Table implements RelationalModel, Serializable { return columns.values().iterator(); } - public Iterator getIndexIterator() { + public Iterator getIndexIterator() { return indexes.values().iterator(); } @@ -240,11 +240,11 @@ public class Table implements RelationalModel, Serializable { return foreignKeys.values().iterator(); } - public Iterator getUniqueKeyIterator() { + public Iterator getUniqueKeyIterator() { return getUniqueKeys().values().iterator(); } - Map getUniqueKeys() { + Map getUniqueKeys() { cleanseUniqueKeyMapIfNeeded(); return uniqueKeys; } @@ -394,6 +394,9 @@ public class Table implements RelationalModel, Serializable { Iterator iter = getColumnIterator(); List results = new ArrayList(); + + int uniqueIndexInteger = 0; + while ( iter.hasNext() ) { Column column = (Column) iter.next(); @@ -420,8 +423,9 @@ public class Table implements RelationalModel, Serializable { } if ( column.isUnique() ) { + uniqueIndexInteger++; UniqueKey uk = getOrCreateUniqueKey( - column.getQuotedName( dialect ) + '_' ); + "UK_" + name + "_" + uniqueIndexInteger); uk.addColumn( column ); alter.append( dialect.getUniqueDelegate() .applyUniqueToColumn( column ) ); @@ -489,7 +493,8 @@ public class Table implements RelationalModel, Serializable { pkname = ( (Column) getPrimaryKey().getColumnIterator().next() ).getQuotedName( dialect ); } - Iterator iter = getSortedColumnIterator(); + Iterator iter = getColumnIterator(); + int uniqueIndexInteger = 0; while ( iter.hasNext() ) { Column col = (Column) iter.next(); @@ -523,8 +528,9 @@ public class Table implements RelationalModel, Serializable { } if ( col.isUnique() ) { + uniqueIndexInteger++; UniqueKey uk = getOrCreateUniqueKey( - col.getQuotedName( dialect ) + '_' ); + "uc_" + name + "_" + uniqueIndexInteger); uk.addColumn( col ); buf.append( dialect.getUniqueDelegate() .applyUniqueToColumn( col ) ); @@ -599,16 +605,7 @@ public class Table implements RelationalModel, Serializable { } public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { - StringBuilder buf = new StringBuilder( "drop table " ); - if ( dialect.supportsIfExistsBeforeTableName() ) { - buf.append( "if exists " ); - } - buf.append( getQualifiedName( dialect, defaultCatalog, defaultSchema ) ) - .append( dialect.getCascadeConstraintsString() ); - if ( dialect.supportsIfExistsAfterTableName() ) { - buf.append( " if exists" ); - } - return buf.toString(); + return dialect.getDropTableString( getQualifiedName( dialect, defaultCatalog, defaultSchema ) ); } public PrimaryKey getPrimaryKey() { @@ -621,7 +618,7 @@ public class Table implements RelationalModel, Serializable { public Index getOrCreateIndex(String indexName) { - Index index = (Index) indexes.get( indexName ); + Index index = indexes.get( indexName ); if ( index == null ) { index = new Index(); @@ -634,11 +631,11 @@ public class Table implements RelationalModel, Serializable { } public Index getIndex(String indexName) { - return (Index) indexes.get( indexName ); + return indexes.get( indexName ); } public Index addIndex(Index index) { - Index current = (Index) indexes.get( index.getName() ); + Index current = indexes.get( index.getName() ); if ( current != null ) { throw new MappingException( "Index " + index.getName() + " already exists!" ); } @@ -647,7 +644,7 @@ public class Table implements RelationalModel, Serializable { } public UniqueKey addUniqueKey(UniqueKey uniqueKey) { - UniqueKey current = (UniqueKey) uniqueKeys.get( uniqueKey.getName() ); + UniqueKey current = uniqueKeys.get( uniqueKey.getName() ); if ( current != null ) { throw new MappingException( "UniqueKey " + uniqueKey.getName() + " already exists!" ); } @@ -656,18 +653,18 @@ public class Table implements RelationalModel, Serializable { } public UniqueKey createUniqueKey(List keyColumns) { - String keyName = "UK" + uniqueColumnString( keyColumns.iterator() ); + String keyName = "UK_" + uniqueColumnString( keyColumns.iterator() ); UniqueKey uk = getOrCreateUniqueKey( keyName ); uk.addColumns( keyColumns.iterator() ); return uk; } public UniqueKey getUniqueKey(String keyName) { - return (UniqueKey) uniqueKeys.get( keyName ); + return uniqueKeys.get( keyName ); } public UniqueKey getOrCreateUniqueKey(String keyName) { - UniqueKey uk = (UniqueKey) uniqueKeys.get( keyName ); + UniqueKey uk = uniqueKeys.get( keyName ); if ( uk == null ) { uk = new UniqueKey(); @@ -733,6 +730,7 @@ public class Table implements RelationalModel, Serializable { } + public String getSchema() { return schema; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java index e5613817b9..8dbceb47a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java @@ -22,8 +22,12 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.mapping; +import java.util.*; +import java.util.Map; + import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.Mapping; +import org.hibernate.internal.util.StringHelper; /** * A relational unique key constraint @@ -31,6 +35,7 @@ import org.hibernate.engine.spi.Mapping; * @author Brett Meyer */ public class UniqueKey extends Constraint { + private java.util.Map columnOrderMap = new HashMap( ); @Override public String sqlConstraintString( @@ -57,4 +62,14 @@ public class UniqueKey extends Constraint { this, defaultCatalog, defaultSchema ); } + public void addColumn(Column column, String order) { + addColumn( column ); + if ( StringHelper.isNotEmpty( order ) ) { + columnOrderMap.put( column, order ); + } + } + + public Map getColumnOrderMap() { + return columnOrderMap; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/MetadataSources.java b/hibernate-core/src/main/java/org/hibernate/metamodel/MetadataSources.java index 315d8e20f4..bcb6fa23ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/MetadataSources.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/MetadataSources.java @@ -78,7 +78,6 @@ public class MetadataSources { private final ServiceRegistry serviceRegistry; private final JaxbMappingProcessor jaxbProcessor; private final List externalCacheRegionDefinitions = new ArrayList(); - private List jaxbRootList = new ArrayList(); private LinkedHashSet> annotatedClasses = new LinkedHashSet>(); private LinkedHashSet annotatedClassNames = new LinkedHashSet(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/relational/UniqueKey.java b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/relational/UniqueKey.java index 0d8dc17160..a42985f829 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/relational/UniqueKey.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/relational/UniqueKey.java @@ -24,6 +24,7 @@ package org.hibernate.metamodel.spi.relational; import org.hibernate.dialect.Dialect; +import org.hibernate.internal.util.StringHelper; /** * Models a SQL INDEX defined as UNIQUE diff --git a/hibernate-core/src/main/java/org/hibernate/param/NamedParameterSpecification.java b/hibernate-core/src/main/java/org/hibernate/param/NamedParameterSpecification.java index 3d1933e539..90d08fb14f 100644 --- a/hibernate-core/src/main/java/org/hibernate/param/NamedParameterSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/param/NamedParameterSpecification.java @@ -62,7 +62,7 @@ public class NamedParameterSpecification extends AbstractExplicitParameterSpecif */ public int bind(PreparedStatement statement, QueryParameters qp, SessionImplementor session, int position) throws SQLException { - TypedValue typedValue = ( TypedValue ) qp.getNamedParameters().get( name ); + TypedValue typedValue = qp.getNamedParameters().get( name ); typedValue.getType().nullSafeSet( statement, typedValue.getValue(), position, session ); return typedValue.getType().getColumnSpan( session.getFactory() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 47513d6071..82adc90c8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -33,8 +33,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import org.jboss.logging.Logger; - import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.HibernateException; @@ -47,6 +45,7 @@ import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.cache.spi.entry.StructuredCollectionCacheEntry; import org.hibernate.cache.spi.entry.StructuredMapCacheEntry; import org.hibernate.cache.spi.entry.UnstructuredCacheEntry; +import org.hibernate.cfg.Configuration; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; import org.hibernate.engine.FetchStyle; @@ -116,6 +115,7 @@ import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.PrimitiveType; import org.hibernate.type.Type; +import org.jboss.logging.Logger; /** * Base implementation of the QueryableCollection interface. @@ -197,7 +197,7 @@ public abstract class AbstractCollectionPersister protected final boolean hasIdentifier; private final boolean isLazy; private final boolean isExtraLazy; - private final boolean isInverse; + protected final boolean isInverse; private final boolean isMutable; private final boolean isVersioned; protected final int batchSize; @@ -210,7 +210,7 @@ public abstract class AbstractCollectionPersister private final String entityName; private final Dialect dialect; - private final SqlExceptionHelper sqlExceptionHelper; + protected final SqlExceptionHelper sqlExceptionHelper; private final SessionFactoryImplementor factory; private final EntityPersister ownerPersister; private final IdentifierGenerator identifierGenerator; @@ -624,7 +624,7 @@ public abstract class AbstractCollectionPersister if ( columnName == null ) { // if the column name is null, it indicates that this index in the property value mapping is // actually represented by a formula. - final int propertyIndex = elementPersister.getEntityMetamodel().getPropertyIndex( reference ); +// final int propertyIndex = elementPersister.getEntityMetamodel().getPropertyIndex( reference ); final String formulaTemplate = formulaTemplates[i]; result[i] = new FormulaReference() { @Override @@ -1571,7 +1571,7 @@ public abstract class AbstractCollectionPersister .addToBatch(); } else { - expectation.verifyOutcome( st.executeUpdate(), st, -1 ); + expectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); } } catch ( SQLException sqle ) { @@ -1582,7 +1582,7 @@ public abstract class AbstractCollectionPersister } finally { if ( !useBatch ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } @@ -1601,7 +1601,7 @@ public abstract class AbstractCollectionPersister } - private BasicBatchKey recreateBatchKey; + protected BasicBatchKey recreateBatchKey; public void recreate(PersistentCollection collection, Serializable id, SessionImplementor session) throws HibernateException { @@ -1670,7 +1670,7 @@ public abstract class AbstractCollectionPersister .addToBatch(); } else { - expectation.verifyOutcome( st.executeUpdate(), st, -1 ); + expectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); } collection.afterRowInsert( this, entry, i ); @@ -1684,7 +1684,7 @@ public abstract class AbstractCollectionPersister } finally { if ( !useBatch ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } @@ -1784,7 +1784,7 @@ public abstract class AbstractCollectionPersister .addToBatch(); } else { - expectation.verifyOutcome( st.executeUpdate(), st, -1 ); + expectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); } count++; } @@ -1796,7 +1796,7 @@ public abstract class AbstractCollectionPersister } finally { if ( !useBatch ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } @@ -1885,7 +1885,7 @@ public abstract class AbstractCollectionPersister session.getTransactionCoordinator().getJdbcCoordinator().getBatch( insertBatchKey ).addToBatch(); } else { - expectation.verifyOutcome( st.executeUpdate(), st, -1 ); + expectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); } collection.afterRowInsert( this, entry, i ); count++; @@ -1898,7 +1898,7 @@ public abstract class AbstractCollectionPersister } finally { if ( !useBatch ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } } @@ -2190,16 +2190,16 @@ public abstract class AbstractCollectionPersister .prepareStatement( sqlSelectSizeString ); try { getKeyType().nullSafeSet( st, key, 1, session ); - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { return rs.next() ? rs.getInt( 1 ) - baseIndex : 0; } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch ( SQLException sqle ) { @@ -2229,19 +2229,19 @@ public abstract class AbstractCollectionPersister try { getKeyType().nullSafeSet( st, key, 1, session ); indexOrElementType.nullSafeSet( st, indexOrElement, keyColumnNames.length + 1, session ); - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { return rs.next(); } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } catch ( TransientObjectException e ) { return false; } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch ( SQLException sqle ) { @@ -2263,7 +2263,7 @@ public abstract class AbstractCollectionPersister try { getKeyType().nullSafeSet( st, key, 1, session ); getIndexType().nullSafeSet( st, incrementIndexByBase( index ), keyColumnNames.length + 1, session ); - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { if ( rs.next() ) { return getElementType().nullSafeGet( rs, elementColumnAliases, session, owner ); @@ -2273,11 +2273,11 @@ public abstract class AbstractCollectionPersister } } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch ( SQLException sqle ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java index 9af95ba2c2..60c29ce63c 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java @@ -272,7 +272,7 @@ public class BasicCollectionPersister extends AbstractCollectionPersister { .addToBatch(); } else { - expectation.verifyOutcome( st.executeUpdate(), st, -1 ); + expectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); } } catch ( SQLException sqle ) { @@ -283,7 +283,7 @@ public class BasicCollectionPersister extends AbstractCollectionPersister { } finally { if ( !useBatch ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } count++; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java index ea262a61ec..7c4831d1a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java @@ -29,6 +29,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Iterator; +import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; @@ -43,7 +44,6 @@ import org.hibernate.internal.FilterAliasGenerator; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectations; -import org.hibernate.loader.collection.BatchingCollectionInitializer; import org.hibernate.loader.collection.BatchingCollectionInitializerBuilder; import org.hibernate.loader.collection.CollectionInitializer; import org.hibernate.loader.collection.SubselectOneToManyLoader; @@ -63,6 +63,7 @@ import org.hibernate.sql.Update; * Collection persister for one-to-many associations. * * @author Gavin King + * @author Brett Meyer */ public class OneToManyPersister extends AbstractCollectionPersister { @@ -159,11 +160,20 @@ public class OneToManyPersister extends AbstractCollectionPersister { } /** - * Not needed for one-to-many association + * Generate the SQL UPDATE that inserts a collection index */ @Override protected String generateUpdateRowString() { - return null; + Update update = new Update( getDialect() ).setTableName( qualifiedTableName ); + update.addPrimaryKeyColumns( elementColumnNames, elementColumnIsSettable, elementColumnWriters ); + if ( hasIdentifier ) { + update.addPrimaryKeyColumns( new String[]{ identifierColumnName } ); + } + if ( hasIndex && !indexContainsFormula ) { + update.addColumns( indexColumnNames ); + } + + return update.toStatementString(); } /** @@ -190,6 +200,105 @@ public class OneToManyPersister extends AbstractCollectionPersister { return update.addPrimaryKeyColumns( rowSelectColumnNames ) .toStatementString(); } + + @Override + public void recreate(PersistentCollection collection, Serializable id, SessionImplementor session) + throws HibernateException { + super.recreate( collection, id, session ); + writeIndex( collection, id, session ); + } + + @Override + public void insertRows(PersistentCollection collection, Serializable id, SessionImplementor session) + throws HibernateException { + super.insertRows( collection, id, session ); + writeIndex( collection, id, session ); + } + + private void writeIndex(PersistentCollection collection, Serializable id, SessionImplementor session) { + // If one-to-many and inverse, still need to create the index. See HHH-5732. + if ( isInverse && hasIndex && !indexContainsFormula ) { + try { + Iterator entries = collection.entries( this ); + if ( entries.hasNext() ) { + Expectation expectation = Expectations.appropriateExpectation( getUpdateCheckStyle() ); + int i = 0; + int count = 0; + while ( entries.hasNext() ) { + + final Object entry = entries.next(); + if ( collection.entryExists( entry, i ) ) { + int offset = 1; + PreparedStatement st = null; + boolean callable = isUpdateCallable(); + boolean useBatch = expectation.canBeBatched(); + String sql = getSQLUpdateRowString(); + + if ( useBatch ) { + if ( recreateBatchKey == null ) { + recreateBatchKey = new BasicBatchKey( + getRole() + "#RECREATE", + expectation + ); + } + st = session.getTransactionCoordinator() + .getJdbcCoordinator() + .getBatch( recreateBatchKey ) + .getBatchStatement( sql, callable ); + } + else { + st = session.getTransactionCoordinator() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql, callable ); + } + + try { + offset += expectation.prepare( st ); + if ( hasIdentifier ) { + offset = writeIdentifier( st, collection.getIdentifier( entry, i ), offset, session ); + } + offset = writeIndex( st, collection.getIndex( entry, i, this ), offset, session ); + offset = writeElement( st, collection.getElement( entry ), offset, session ); + + if ( useBatch ) { + session.getTransactionCoordinator() + .getJdbcCoordinator() + .getBatch( recreateBatchKey ) + .addToBatch(); + } + else { + expectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); + } + count++; + } + catch ( SQLException sqle ) { + if ( useBatch ) { + session.getTransactionCoordinator().getJdbcCoordinator().abortBatch(); + } + throw sqle; + } + finally { + if ( !useBatch ) { + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); + } + } + + } + i++; + } + } + } + catch ( SQLException sqle ) { + throw sqlExceptionHelper.convert( + sqle, + "could not update collection: " + + MessageHelper.collectionInfoString( this, collection, id, session ), + getSQLUpdateRowString() + ); + } + } + } public boolean consumesEntityAlias() { return true; @@ -259,7 +368,7 @@ public class OneToManyPersister extends AbstractCollectionPersister { .addToBatch(); } else { - deleteExpectation.verifyOutcome( st.executeUpdate(), st, -1 ); + deleteExpectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); } count++; } @@ -274,7 +383,7 @@ public class OneToManyPersister extends AbstractCollectionPersister { } finally { if ( !useBatch ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } } @@ -326,7 +435,7 @@ public class OneToManyPersister extends AbstractCollectionPersister { session.getTransactionCoordinator().getJdbcCoordinator().getBatch( insertRowBatchKey ).addToBatch(); } else { - insertExpectation.verifyOutcome( st.executeUpdate(), st, -1 ); + insertExpectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ), st, -1 ); } count++; } @@ -341,7 +450,7 @@ public class OneToManyPersister extends AbstractCollectionPersister { } finally { if ( !useBatch ) { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 42fe5e1abb..8b9a1211ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -894,7 +894,7 @@ public abstract class AbstractEntityPersister } else { rootTableKeyColumnReaders[i] = col.getReadFragment(); - rootTableKeyColumnReaderTemplates[i] = getTemplateFromString( col.getReadFragment(), factory ); + rootTableKeyColumnReaderTemplates[i] = getTemplateFromString( rootTableKeyColumnReaders[i], factory ); } identifierAliases[i] = col.getAlias( factory.getDialect(), entityBinding.getHierarchyDetails().getRootEntityBinding().getPrimaryTable() ); } @@ -1272,8 +1272,146 @@ public abstract class AbstractEntityPersister @Override public Object initializeLazyProperty(String fieldName, Object entity, SessionImplementor session) throws HibernateException { - return lazyPropertyInitializerDelegater.initializeLazyProperty( fieldName, entity, session ); +// return lazyPropertyInitializerDelegater.initializeLazyProperty( fieldName, entity, session ); +// +//<<<<<<< HEAD +//======= + final Serializable id = session.getContextEntityIdentifier( entity ); + final EntityEntry entry = session.getPersistenceContext().getEntry( entity ); + if ( entry == null ) { + throw new HibernateException( "entity is not associated with the session: " + id ); + } + + if ( LOG.isTraceEnabled() ) { + LOG.tracev( "Initializing lazy properties of: {0}, field access: {1}", MessageHelper.infoString( this, id, getFactory() ), fieldName ); + } + + if ( hasCache() ) { + CacheKey cacheKey = session.generateCacheKey( id, getIdentifierType(), getEntityName() ); + Object ce = getCacheAccessStrategy().get( cacheKey, session.getTimestamp() ); + if (ce!=null) { + CacheEntry cacheEntry = (CacheEntry) getCacheEntryStructure().destructure(ce, factory); + if ( !cacheEntry.areLazyPropertiesUnfetched() ) { + //note early exit here: + return initializeLazyPropertiesFromCache( fieldName, entity, session, entry, cacheEntry ); + } + } + } + + return initializeLazyPropertiesFromDatastore( fieldName, entity, session, id, entry ); + + } + + private Object initializeLazyPropertiesFromDatastore( + final String fieldName, + final Object entity, + final SessionImplementor session, + final Serializable id, + final EntityEntry entry) { + + if ( !hasLazyProperties() ) throw new AssertionFailure( "no lazy properties" ); + + LOG.trace( "Initializing lazy properties from datastore" ); + + try { + + Object result = null; + PreparedStatement ps = null; + try { + final String lazySelect = getSQLLazySelectString(); + ResultSet rs = null; + try { + if ( lazySelect != null ) { + // null sql means that the only lazy properties + // are shared PK one-to-one associations which are + // handled differently in the Type#nullSafeGet code... + ps = session.getTransactionCoordinator() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( lazySelect ); + getIdentifierType().nullSafeSet( ps, id, 1, session ); + rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); + rs.next(); + } + final Object[] snapshot = entry.getLoadedState(); + for ( int j = 0; j < lazyPropertyNames.length; j++ ) { + Object propValue = lazyPropertyTypes[j].nullSafeGet( rs, lazyPropertyColumnAliases[j], session, entity ); + if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) { + result = propValue; + } + } + } + finally { + if ( rs != null ) { + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); + } + } + } + finally { + if ( ps != null ) { + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); + } + } + + LOG.trace( "Done initializing lazy properties" ); + + return result; + + } + catch ( SQLException sqle ) { + throw getFactory().getSQLExceptionHelper().convert( + sqle, + "could not initialize lazy properties: " + + MessageHelper.infoString( this, id, getFactory() ), + getSQLLazySelectString() + ); + } + } + + private Object initializeLazyPropertiesFromCache( + final String fieldName, + final Object entity, + final SessionImplementor session, + final EntityEntry entry, + final CacheEntry cacheEntry + ) { + + LOG.trace( "Initializing lazy properties from second-level cache" ); + + Object result = null; + Serializable[] disassembledValues = cacheEntry.getDisassembledState(); + final Object[] snapshot = entry.getLoadedState(); + for ( int j = 0; j < lazyPropertyNames.length; j++ ) { + final Object propValue = lazyPropertyTypes[j].assemble( + disassembledValues[ lazyPropertyNumbers[j] ], + session, + entity + ); + if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) { + result = propValue; + } + } + + LOG.trace( "Done initializing lazy properties" ); + + return result; + } + + private boolean initializeLazyProperty( + final String fieldName, + final Object entity, + final SessionImplementor session, + final Object[] snapshot, + final int j, + final Object propValue) { + setPropertyValue( entity, lazyPropertyNumbers[j], propValue ); + if ( snapshot != null ) { + // object have been loaded with setReadOnly(true); HHH-2236 + snapshot[ lazyPropertyNumbers[j] ] = lazyPropertyTypes[j].deepCopy( propValue, factory ); + } + return fieldName.equals( lazyPropertyNames[j] ); +//>>>>>>> master } public boolean isBatchable() { @@ -1446,7 +1584,7 @@ public abstract class AbstractEntityPersister try { getIdentifierType().nullSafeSet( ps, id, 1, session ); //if ( isVersioned() ) getVersionType().nullSafeSet( ps, version, getIdentifierColumnSpan()+1, session ); - ResultSet rs = ps.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); try { //if there is no resulting row, return null if ( !rs.next() ) { @@ -1464,11 +1602,11 @@ public abstract class AbstractEntityPersister return values; } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } catch ( SQLException e ) { @@ -1506,7 +1644,7 @@ public abstract class AbstractEntityPersister .prepareStatement( generateIdByUniqueKeySelectString( uniquePropertyName ) ); try { propertyType.nullSafeSet( ps, key, 1, session ); - ResultSet rs = ps.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); try { //if there is no resulting row, return null if ( !rs.next() ) { @@ -1515,11 +1653,11 @@ public abstract class AbstractEntityPersister return (Serializable) getIdentifierType().nullSafeGet( rs, getIdentifierAliases(), session, null ); } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } catch ( SQLException e ) { @@ -1763,13 +1901,13 @@ public abstract class AbstractEntityPersister getVersionType().nullSafeSet( st, nextVersion, 1, session ); getIdentifierType().nullSafeSet( st, id, 2, session ); getVersionType().nullSafeSet( st, currentVersion, 2 + getIdentifierColumnSpan(), session ); - int rows = st.executeUpdate(); + int rows = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( st ); if ( rows != 1 ) { throw new StaleObjectStateException( getEntityName(), id ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch ( SQLException sqle ) { @@ -1812,7 +1950,7 @@ public abstract class AbstractEntityPersister .prepareStatement( getVersionSelectString() ); try { getIdentifierType().nullSafeSet( st, id, 1, session ); - ResultSet rs = st.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st ); try { if ( !rs.next() ) { return null; @@ -1823,11 +1961,11 @@ public abstract class AbstractEntityPersister return getVersionType().nullSafeGet( rs, getVersionColumnName(), session, null ); } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - st.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } catch ( SQLException e ) { @@ -1843,6 +1981,7 @@ public abstract class AbstractEntityPersister lockers.put( LockMode.READ, generateLocker( LockMode.READ ) ); lockers.put( LockMode.UPGRADE, generateLocker( LockMode.UPGRADE ) ); lockers.put( LockMode.UPGRADE_NOWAIT, generateLocker( LockMode.UPGRADE_NOWAIT ) ); + lockers.put( LockMode.UPGRADE_SKIPLOCKED, generateLocker( LockMode.UPGRADE_SKIPLOCKED ) ); lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) ); lockers.put( LockMode.PESSIMISTIC_READ, generateLocker( LockMode.PESSIMISTIC_READ ) ); lockers.put( LockMode.PESSIMISTIC_WRITE, generateLocker( LockMode.PESSIMISTIC_WRITE ) ); @@ -2836,7 +2975,7 @@ public abstract class AbstractEntityPersister .getStatementPreparer() .prepareStatement( sql ); rootPersister.getIdentifierType().nullSafeSet( sequentialSelect, id, 1, session ); - sequentialResultSet = sequentialSelect.executeQuery(); + sequentialResultSet = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( sequentialSelect ); if ( !sequentialResultSet.next() ) { // TODO: Deal with the "optional" attribute in the mapping; // this code assumes that optional defaults to "true" because it @@ -2893,7 +3032,7 @@ public abstract class AbstractEntityPersister } if ( sequentialResultSet != null ) { - sequentialResultSet.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( sequentialResultSet ); } return values; @@ -2901,7 +3040,7 @@ public abstract class AbstractEntityPersister } finally { if ( sequentialSelect != null ) { - sequentialSelect.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( sequentialSelect ); } } } @@ -3051,7 +3190,7 @@ public abstract class AbstractEntityPersister session.getTransactionCoordinator().getJdbcCoordinator().getBatch( inserBatchKey ).addToBatch(); } else { - expectation.verifyOutcome( insert.executeUpdate(), insert, -1 ); + expectation.verifyOutcome( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( insert ), insert, -1 ); } } @@ -3063,7 +3202,7 @@ public abstract class AbstractEntityPersister } finally { if ( !useBatch ) { - insert.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( insert ); } } } @@ -3210,7 +3349,7 @@ public abstract class AbstractEntityPersister return true; } else { - return check( update.executeUpdate(), id, j, expectation, update ); + return check( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( update ), id, j, expectation, update ); } } @@ -3222,7 +3361,7 @@ public abstract class AbstractEntityPersister } finally { if ( !useBatch ) { - update.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( update ); } } @@ -3328,7 +3467,7 @@ public abstract class AbstractEntityPersister session.getTransactionCoordinator().getJdbcCoordinator().getBatch( deleteBatchKey ).addToBatch(); } else { - check( delete.executeUpdate(), id, j, expectation, delete ); + check( session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( delete ), id, j, expectation, delete ); } } @@ -3340,7 +3479,7 @@ public abstract class AbstractEntityPersister } finally { if ( !useBatch ) { - delete.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( delete ); } } @@ -3855,6 +3994,12 @@ public abstract class AbstractEntityPersister readLoader : createEntityLoader( LockMode.UPGRADE_NOWAIT ) ); + loaders.put( + LockMode.UPGRADE_SKIPLOCKED, + disableForUpdate ? + readLoader : + createEntityLoader( LockMode.UPGRADE_SKIPLOCKED ) + ); loaders.put( LockMode.FORCE, disableForUpdate ? @@ -4641,7 +4786,7 @@ public abstract class AbstractEntityPersister .prepareStatement( selectionSQL ); try { getIdentifierType().nullSafeSet( ps, id, 1, session ); - ResultSet rs = ps.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); try { if ( !rs.next() ) { throw new HibernateException( @@ -4659,12 +4804,12 @@ public abstract class AbstractEntityPersister } finally { if ( rs != null ) { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } } finally { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } catch( SQLException e ) { @@ -4741,7 +4886,7 @@ public abstract class AbstractEntityPersister .prepareStatement( sql ); try { getIdentifierType().nullSafeSet( ps, id, 1, session ); - ResultSet rs = ps.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); try { //if there is no resulting row, return null if ( !rs.next() ) { @@ -4758,11 +4903,11 @@ public abstract class AbstractEntityPersister return snapshot; } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } catch ( SQLException e ) { @@ -4806,7 +4951,7 @@ public abstract class AbstractEntityPersister positions += type.getColumnSpan( session.getFactory() ); } } - ResultSet rs = ps.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); try { // if there is no resulting row, return null if ( !rs.next() ) { @@ -4816,11 +4961,11 @@ public abstract class AbstractEntityPersister return (Serializable) getIdentifierType().hydrate( rs, getIdentifierAliases(), session, null ); } finally { - rs.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( rs ); } } finally { - ps.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } catch ( SQLException e ) { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/NamedParametersNotSupportedException.java b/hibernate-core/src/main/java/org/hibernate/procedure/NamedParametersNotSupportedException.java new file mode 100644 index 0000000000..1929c10963 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/NamedParametersNotSupportedException.java @@ -0,0 +1,38 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure; + +import org.hibernate.HibernateException; + +/** + * Thrown to indicate that an attempt was made to register a stored procedure named parameter, but the underlying + * database reports to not support named parameters. + * + * @author Steve Ebersole + */ +public class NamedParametersNotSupportedException extends HibernateException { + public NamedParametersNotSupportedException(String message) { + super( message ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java new file mode 100644 index 0000000000..dfbb5d2033 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure; + +import javax.persistence.TemporalType; + +/** + * Describes an input value binding for any IN/INOUT parameters. + */ +public interface ParameterBind { + /** + * Retrieves the bound value. + * + * @return The bound value. + */ + public T getValue(); + + /** + * If {@code } represents a DATE/TIME type value, JPA usually allows specifying the particular parts of + * the DATE/TIME value to be bound. This value represents the particular part the user requested to be bound. + * + * @return The explicitly supplied TemporalType. + */ + public TemporalType getExplicitTemporalType(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterMisuseException.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterMisuseException.java new file mode 100644 index 0000000000..472f17a7a5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterMisuseException.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure; + +import org.hibernate.HibernateException; + +/** + * Thrown to indicate a misuse of a {@link ParameterRegistration} + * + * @author Steve Ebersole + */ +public class ParameterMisuseException extends HibernateException { + public ParameterMisuseException(String message) { + super( message ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java new file mode 100644 index 0000000000..174c31cc92 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure; + +import javax.persistence.ParameterMode; +import javax.persistence.TemporalType; + +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public interface ParameterRegistration { + /** + * The name under which this parameter was registered. Can be {@code null} which should indicate that + * positional registration was used (and therefore {@link #getPosition()} should return non-null. + * + * @return The name; + */ + public String getName(); + + /** + * The position at which this parameter was registered. Can be {@code null} which should indicate that + * named registration was used (and therefore {@link #getName()} should return non-null. + * + * @return The name; + */ + public Integer getPosition(); + + /** + * Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType} + * is called explicitly). + * + * @return The parameter Java type. + */ + public Class getType(); + + /** + * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure + * definition (is it an INPUT parameter? An OUTPUT parameter? etc). + * + * @return The parameter mode. + */ + public ParameterMode getMode(); + + /** + * Set the Hibernate mapping type for this parameter. + * + * @param type The Hibernate mapping type. + */ + public void setHibernateType(Type type); + + /** + * Retrieve the binding associated with this parameter. The binding is only relevant for INPUT parameters. Can + * return {@code null} if nothing has been bound yet. To bind a value to the parameter use one of the + * {@link #bindValue} methods. + * + * @return The parameter binding + */ + public ParameterBind getParameterBind(); + + /** + * Bind a value to the parameter. How this value is bound to the underlying JDBC CallableStatement is + * totally dependent on the Hibernate type. + * + * @param value The value to bind. + */ + public void bindValue(T value); + + /** + * Bind a value to the parameter, using just a specified portion of the DATE/TIME value. It is illegal to call + * this form if the parameter is not DATE/TIME type. The Hibernate type is circumvented in this case and + * an appropriate "precision" Type is used instead. + * + * @param value The value to bind + * @param explicitTemporalType An explicitly supplied TemporalType. + */ + public void bindValue(T value, TemporalType explicitTemporalType); +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java new file mode 100644 index 0000000000..3ad02c4013 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java @@ -0,0 +1,136 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure; + +import javax.persistence.ParameterMode; +import java.util.List; + +import org.hibernate.BasicQueryContract; +import org.hibernate.MappingException; +import org.hibernate.SynchronizeableQuery; + +/** + * Defines support for executing database stored procedures and functions + * + * @author Steve Ebersole + */ +public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery { + @Override + public ProcedureCall addSynchronizedQuerySpace(String querySpace); + + @Override + public ProcedureCall addSynchronizedEntityName(String entityName) throws MappingException; + + @Override + public ProcedureCall addSynchronizedEntityClass(Class entityClass) throws MappingException; + + /** + * Get the name of the stored procedure to be called. + * + * @return The procedure name. + */ + public String getProcedureName(); + + /** + * Basic form for registering a positional parameter. + * + * @param position The position + * @param type The Java type of the parameter + * @param mode The parameter mode (in, out, inout) + * + * @return The parameter registration memento + */ + public ParameterRegistration registerParameter(int position, Class type, ParameterMode mode); + + /** + * Chained form of {@link #registerParameter(int, Class, javax.persistence.ParameterMode)} + * + * @param position The position + * @param type The Java type of the parameter + * @param mode The parameter mode (in, out, inout) + * + * @return {@code this}, for method chaining + */ + public ProcedureCall registerParameter0(int position, Class type, ParameterMode mode); + + /** + * Retrieve a previously registered parameter memento by the position under which it was registered. + * + * @param position The parameter position + * + * @return The parameter registration memento + */ + public ParameterRegistration getParameterRegistration(int position); + + /** + * Basic form for registering a named parameter. + * + * @param parameterName The parameter name + * @param type The Java type of the parameter + * @param mode The parameter mode (in, out, inout) + * + * @return The parameter registration memento + */ + public ParameterRegistration registerParameter(String parameterName, Class type, ParameterMode mode) + throws NamedParametersNotSupportedException; + + /** + * Chained form of {@link #registerParameter(String, Class, javax.persistence.ParameterMode)} + * + * @param parameterName The parameter name + * @param type The Java type of the parameter + * @param mode The parameter mode (in, out, inout) + * + * @return The parameter registration memento + */ + public ProcedureCall registerParameter0(String parameterName, Class type, ParameterMode mode) + throws NamedParametersNotSupportedException; + + /** + * Retrieve a previously registered parameter memento by the name under which it was registered. + * + * @param name The parameter name + * + * @return The parameter registration memento + */ + public ParameterRegistration getParameterRegistration(String name); + + /** + * Retrieve all registered parameters. + * + * @return The (immutable) list of all registered parameters. + */ + public List getRegisteredParameters(); + + /** + * Retrieves access to outputs of this procedure call. Can be called multiple times, returning the same + * Output instance each time. + *

+ * Note that the procedure will not actually be executed until the outputs are actually accessed. + * + * @return The outputs representation + */ + public ProcedureResult getResult(); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureOutputs.java b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureResult.java similarity index 63% rename from hibernate-core/src/main/java/org/hibernate/StoredProcedureOutputs.java rename to hibernate-core/src/main/java/org/hibernate/procedure/ProcedureResult.java index 7591c7d415..c680d9e39e 100644 --- a/hibernate-core/src/main/java/org/hibernate/StoredProcedureOutputs.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureResult.java @@ -21,15 +21,31 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate; +package org.hibernate.procedure; + +import org.hibernate.result.Result; /** - * Represents all the outputs of a call to a database stored procedure (or function) through the JDBC - * {@link java.sql.CallableStatement} interface. + * Specialization of the {@link Result} contract providing access to the stored procedure's registered + * output parameters. * * @author Steve Ebersole */ -public interface StoredProcedureOutputs { +public interface ProcedureResult extends Result { + /** + * Retrieve the value of an OUTPUT parameter by the parameter's registration memento. + *

+ * Should NOT be called for parameters registered as REF_CURSOR. REF_CURSOR parameters should be + * accessed via the returns (see {@link #getNextReturn} + * + * @param parameterRegistration The parameter's registration memento. + * + * @return The output value. + * + * @see ProcedureCall#registerParameter(String, Class, javax.persistence.ParameterMode) + */ + public T getOutputParameterValue(ParameterRegistration parameterRegistration); + /** * Retrieve the value of an OUTPUT parameter by the name under which the parameter was registered. * @@ -37,7 +53,7 @@ public interface StoredProcedureOutputs { * * @return The output value. * - * @see StoredProcedureCall#registerStoredProcedureParameter(String, Class, javax.persistence.ParameterMode) + * @see ProcedureCall#registerParameter(String, Class, javax.persistence.ParameterMode) */ public Object getOutputParameterValue(String name); @@ -48,22 +64,7 @@ public interface StoredProcedureOutputs { * * @return The output value. * - * @see StoredProcedureCall#registerStoredProcedureParameter(int, Class, javax.persistence.ParameterMode) + * @see ProcedureCall#registerParameter(int, Class, javax.persistence.ParameterMode) */ public Object getOutputParameterValue(int position); - - /** - * Are there any more returns associated with this set of outputs? - * - * @return {@code true} means there are more results available via {@link #getNextReturn()}; {@code false} - * indicates that calling {@link #getNextReturn()} will certainly result in an exception. - */ - public boolean hasMoreReturns(); - - /** - * Retrieve the next return. - * - * @return The next return. - */ - public StoredProcedureReturn getNextReturn(); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java new file mode 100644 index 0000000000..5a75e403d4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java @@ -0,0 +1,267 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +import javax.persistence.ParameterMode; +import javax.persistence.TemporalType; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.util.Calendar; +import java.util.Date; + +import org.jboss.logging.Logger; + +import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.procedure.ParameterBind; +import org.hibernate.procedure.ParameterMisuseException; +import org.hibernate.type.DateType; +import org.hibernate.type.ProcedureParameterExtractionAware; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractParameterRegistrationImpl implements ParameterRegistrationImplementor { + private static final Logger log = Logger.getLogger( AbstractParameterRegistrationImpl.class ); + + private final ProcedureCallImpl procedureCall; + + private final Integer position; + private final String name; + + private final ParameterMode mode; + private final Class type; + + private ParameterBindImpl bind; + + private int startIndex; + private Type hibernateType; + private int[] sqlTypes; + + protected AbstractParameterRegistrationImpl( + ProcedureCallImpl procedureCall, + Integer position, + Class type, + ParameterMode mode) { + this( procedureCall, position, null, type, mode ); + } + + protected AbstractParameterRegistrationImpl( + ProcedureCallImpl procedureCall, + String name, + Class type, + ParameterMode mode) { + this( procedureCall, null, name, type, mode ); + } + + private AbstractParameterRegistrationImpl( + ProcedureCallImpl procedureCall, + Integer position, + String name, + Class type, + ParameterMode mode) { + this.procedureCall = procedureCall; + + this.position = position; + this.name = name; + + this.mode = mode; + this.type = type; + + setHibernateType( session().getFactory().getTypeResolver().heuristicType( type.getName() ) ); + } + + protected SessionImplementor session() { + return procedureCall.getSession(); + } + + @Override + public String getName() { + return name; + } + + @Override + public Integer getPosition() { + return position; + } + + @Override + public Class getType() { + return type; + } + + @Override + public ParameterMode getMode() { + return mode; + } + + @Override + public void setHibernateType(Type type) { + if ( type == null ) { + throw new IllegalArgumentException( "Type cannot be null" ); + } + this.hibernateType = type; + this.sqlTypes = hibernateType.sqlTypes( session().getFactory() ); + } + + @Override + public ParameterBind getParameterBind() { + return bind; + } + + @Override + public void bindValue(T value) { + validateBindability(); + this.bind = new ParameterBindImpl( value ); + } + + private void validateBindability() { + if ( ! canBind() ) { + throw new ParameterMisuseException( "Cannot bind value to non-input parameter : " + this ); + } + } + + private boolean canBind() { + return mode == ParameterMode.IN || mode == ParameterMode.INOUT; + } + + @Override + public void bindValue(T value, TemporalType explicitTemporalType) { + validateBindability(); + if ( explicitTemporalType != null ) { + if ( ! isDateTimeType() ) { + throw new IllegalArgumentException( "TemporalType should not be specified for non date/time type" ); + } + } + this.bind = new ParameterBindImpl( value, explicitTemporalType ); + } + + private boolean isDateTimeType() { + return Date.class.isAssignableFrom( type ) + || Calendar.class.isAssignableFrom( type ); + } + + @Override + public void prepare(CallableStatement statement, int startIndex) throws SQLException { + if ( mode == ParameterMode.REF_CURSOR ) { + throw new NotYetImplementedException( "Support for REF_CURSOR parameters not yet supported" ); + } + + this.startIndex = startIndex; + if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( sqlTypes.length > 1 ) { + if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) + && ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction() ) { + // the type can handle multi-param extraction... + } + else { + // it cannot... + throw new UnsupportedOperationException( + "Type [" + hibernateType + "] does support multi-parameter value extraction" + ); + } + } + for ( int i = 0; i < sqlTypes.length; i++ ) { + statement.registerOutParameter( startIndex + i, sqlTypes[i] ); + } + } + + if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { + if ( bind == null || bind.getValue() == null ) { + // the user did not bind a value to the parameter being processed. That might be ok *if* the + // procedure as defined in the database defines a default value for that parameter. + // Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure + // parameter defines a default value. So we simply allow the procedure execution to happen + // assuming that the database will complain appropriately if not setting the given parameter + // bind value is an error. + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound; assuming procedure defines default value", + procedureCall.getProcedureName(), + this + ); + } + else { + final Type typeToUse; + if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.TIMESTAMP ) { + typeToUse = hibernateType; + } + else if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.DATE ) { + typeToUse = DateType.INSTANCE; + } + else { + typeToUse = hibernateType; + } + typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() ); + } + } + } + else { + // we have a REF_CURSOR type param + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { + session().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, getName() ); + } + else { + session().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, getPosition() ); + } + } + } + + public int[] getSqlTypes() { + return sqlTypes; + } + + @Override + @SuppressWarnings("unchecked") + public T extract(CallableStatement statement) { + if ( mode == ParameterMode.IN ) { + throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); + } + else if ( mode == ParameterMode.REF_CURSOR ) { + throw new ParameterMisuseException( "REF_CURSOR parameters should be accessed via results" ); + } + + try { + if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { + return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( statement, startIndex, session() ); + } + else { + return (T) statement.getObject( startIndex ); + } + } + catch (SQLException e) { + throw procedureCall.getSession().getFactory().getSQLExceptionHelper().convert( + e, + "Unable to extract OUT/INOUT parameter value" + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java new file mode 100644 index 0000000000..d3a772cb70 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +import javax.persistence.ParameterMode; + +/** + * @author Steve Ebersole + */ +public class NamedParameterRegistration extends AbstractParameterRegistrationImpl { + public NamedParameterRegistration( + ProcedureCallImpl procedureCall, + String name, + Class type, + ParameterMode mode) { + super( procedureCall, name, type, mode ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java new file mode 100644 index 0000000000..505bca94f4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +import javax.persistence.TemporalType; + +import org.hibernate.procedure.ParameterBind; + +/** + * Implementation of the {@link ParameterBind} contract. + * + * @author Steve Ebersole + */ +public class ParameterBindImpl implements ParameterBind { + private final T value; + private final TemporalType explicitTemporalType; + + public ParameterBindImpl(T value) { + this( value, null ); + } + + public ParameterBindImpl(T value, TemporalType explicitTemporalType) { + this.value = value; + this.explicitTemporalType = explicitTemporalType; + } + + @Override + public T getValue() { + return value; + } + + @Override + public TemporalType getExplicitTemporalType() { + return explicitTemporalType; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterRegistrationImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterRegistrationImplementor.java new file mode 100644 index 0000000000..0d1992d8a3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterRegistrationImplementor.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +import java.sql.CallableStatement; +import java.sql.SQLException; + +import org.hibernate.procedure.ParameterRegistration; + +/** + * @author Steve Ebersole + */ +public interface ParameterRegistrationImplementor extends ParameterRegistration { + public void prepare(CallableStatement statement, int i) throws SQLException; + + public int[] getSqlTypes(); + + public T extract(CallableStatement statement); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterStrategy.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterStrategy.java new file mode 100644 index 0000000000..bf517dc3f5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterStrategy.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +/** + * The style/strategy of parameter registration used in a particular procedure call definition. + */ +public enum ParameterStrategy { + NAMED, + POSITIONAL, + UNKNOWN +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java new file mode 100644 index 0000000000..a991525c58 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +import javax.persistence.ParameterMode; + +/** + * @author Steve Ebersole + */ +public class PositionalParameterRegistration extends AbstractParameterRegistrationImpl { + public PositionalParameterRegistration( + ProcedureCallImpl procedureCall, + Integer position, + Class type, + ParameterMode mode) { + super( procedureCall, position, type, mode ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java new file mode 100644 index 0000000000..5cef55abb6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -0,0 +1,394 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +import javax.persistence.ParameterMode; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.MappingException; +import org.hibernate.QueryException; +import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.engine.ResultSetMappingDefinition; +import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.AbstractBasicQueryContractImpl; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.NamedParametersNotSupportedException; +import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.procedure.ProcedureResult; +import org.hibernate.result.spi.ResultContext; +import org.hibernate.type.Type; + +/** + * Standard implementation of {@link org.hibernate.procedure.ProcedureCall} + * + * @author Steve Ebersole + */ +public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements ProcedureCall, ResultContext { + private final String procedureName; + private final NativeSQLQueryReturn[] queryReturns; + + private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN; + private List> registeredParameters = new ArrayList>(); + + private Set synchronizedQuerySpaces; + + private ProcedureResultImpl outputs; + + + @SuppressWarnings("unchecked") + public ProcedureCallImpl(SessionImplementor session, String procedureName) { + this( session, procedureName, (List) null ); + } + + public ProcedureCallImpl(SessionImplementor session, String procedureName, List queryReturns) { + super( session ); + this.procedureName = procedureName; + + if ( queryReturns == null || queryReturns.isEmpty() ) { + this.queryReturns = new NativeSQLQueryReturn[0]; + } + else { + this.queryReturns = queryReturns.toArray( new NativeSQLQueryReturn[ queryReturns.size() ] ); + } + } + + public ProcedureCallImpl(SessionImplementor session, String procedureName, Class... resultClasses) { + this( session, procedureName, collectQueryReturns( resultClasses ) ); + } + + private static List collectQueryReturns(Class[] resultClasses) { + if ( resultClasses == null || resultClasses.length == 0 ) { + return null; + } + + List queryReturns = new ArrayList( resultClasses.length ); + int i = 1; + for ( Class resultClass : resultClasses ) { + queryReturns.add( new NativeSQLQueryRootReturn( "alias" + i, resultClass.getName(), LockMode.READ ) ); + i++; + } + return queryReturns; + } + + public ProcedureCallImpl(SessionImplementor session, String procedureName, String... resultSetMappings) { + this( session, procedureName, collectQueryReturns( session, resultSetMappings ) ); + } + + private static List collectQueryReturns(SessionImplementor session, String[] resultSetMappings) { + if ( resultSetMappings == null || resultSetMappings.length == 0 ) { + return null; + } + + List queryReturns = new ArrayList( resultSetMappings.length ); + for ( String resultSetMapping : resultSetMappings ) { + ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( resultSetMapping ); + if ( mapping == null ) { + throw new MappingException( "Unknown SqlResultSetMapping [" + resultSetMapping + "]" ); + } + queryReturns.addAll( Arrays.asList( mapping.getQueryReturns() ) ); + } + return queryReturns; + } + +// public ProcedureCallImpl( +// SessionImplementor session, +// String procedureName, +// List parameters) { +// // this form is intended for named stored procedure calls. +// // todo : introduce a NamedProcedureCallDefinition object to hold all needed info and pass that in here; will help with EM.addNamedQuery as well.. +// this( session, procedureName ); +// for ( StoredProcedureParameter parameter : parameters ) { +// registerParameter( (StoredProcedureParameterImplementor) parameter ); +// } +// } + + @Override + public SessionImplementor getSession() { + return super.session(); + } + + public ParameterStrategy getParameterStrategy() { + return parameterStrategy; + } + + @Override + public String getProcedureName() { + return procedureName; + } + + @Override + public String getSql() { + return getProcedureName(); + } + + @Override + public NativeSQLQueryReturn[] getQueryReturns() { + return queryReturns; + } + + @Override + @SuppressWarnings("unchecked") + public ParameterRegistration registerParameter(int position, Class type, ParameterMode mode) { + final PositionalParameterRegistration parameterRegistration = new PositionalParameterRegistration( this, position, type, mode ); + registerParameter( parameterRegistration ); + return parameterRegistration; + } + + @Override + @SuppressWarnings("unchecked") + public ProcedureCall registerParameter0(int position, Class type, ParameterMode mode) { + registerParameter( position, type, mode ); + return this; + } + + private void registerParameter(ParameterRegistrationImplementor parameter) { + if ( StringHelper.isNotEmpty( parameter.getName() ) ) { + prepareForNamedParameters(); + } + else if ( parameter.getPosition() != null ) { + prepareForPositionalParameters(); + } + else { + throw new IllegalArgumentException( "Given parameter did not define name or position [" + parameter + "]" ); + } + registeredParameters.add( parameter ); + } + + private void prepareForPositionalParameters() { + if ( parameterStrategy == ParameterStrategy.NAMED ) { + throw new QueryException( "Cannot mix named and positional parameters" ); + } + parameterStrategy = ParameterStrategy.POSITIONAL; + } + + private void prepareForNamedParameters() { + if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { + throw new QueryException( "Cannot mix named and positional parameters" ); + } + if ( parameterStrategy == null ) { + // protect to only do this check once + final ExtractedDatabaseMetaData databaseMetaData = getSession().getTransactionCoordinator() + .getJdbcCoordinator() + .getLogicalConnection() + .getJdbcServices() + .getExtractedMetaDataSupport(); + if ( ! databaseMetaData.supportsNamedParameters() ) { + throw new NamedParametersNotSupportedException( + "Named stored procedure parameters used, but JDBC driver does not support named parameters" + ); + } + parameterStrategy = ParameterStrategy.NAMED; + } + } + + @Override + public ParameterRegistrationImplementor getParameterRegistration(int position) { + if ( parameterStrategy != ParameterStrategy.POSITIONAL ) { + throw new IllegalArgumentException( "Positions were not used to register parameters with this stored procedure call" ); + } + try { + return registeredParameters.get( position ); + } + catch ( Exception e ) { + throw new QueryException( "Could not locate parameter registered using that position [" + position + "]" ); + } + } + + @Override + @SuppressWarnings("unchecked") + public ParameterRegistration registerParameter(String name, Class type, ParameterMode mode) { + final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, type, mode ); + registerParameter( parameterRegistration ); + return parameterRegistration; + } + + @Override + @SuppressWarnings("unchecked") + public ProcedureCall registerParameter0(String name, Class type, ParameterMode mode) { + registerParameter( name, type, mode ); + return this; + } + + @Override + public ParameterRegistrationImplementor getParameterRegistration(String name) { + if ( parameterStrategy != ParameterStrategy.NAMED ) { + throw new IllegalArgumentException( "Names were not used to register parameters with this stored procedure call" ); + } + for ( ParameterRegistrationImplementor parameter : registeredParameters ) { + if ( name.equals( parameter.getName() ) ) { + return parameter; + } + } + throw new IllegalArgumentException( "Could not locate parameter registered under that name [" + name + "]" ); + } + + @Override + @SuppressWarnings("unchecked") + public List getRegisteredParameters() { + return new ArrayList( registeredParameters ); + } + + @Override + public ProcedureResult getResult() { + if ( outputs == null ) { + outputs = buildOutputs(); + } + + return outputs; + } + + private ProcedureResultImpl buildOutputs() { + // todo : going to need a very specialized Loader for this. + // or, might be a good time to look at splitting Loader up into: + // 1) building statement objects + // 2) executing statement objects + // 3) processing result sets + + // for now assume there are no resultClasses nor mappings defined.. + // TOTAL PROOF-OF-CONCEPT!!!!!! + + final StringBuilder buffer = new StringBuilder().append( "{call " ) + .append( procedureName ) + .append( "(" ); + String sep = ""; + for ( ParameterRegistrationImplementor parameter : registeredParameters ) { + for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) { + buffer.append( sep ).append( "?" ); + sep = ","; + } + } + buffer.append( ")}" ); + + try { + final CallableStatement statement = (CallableStatement) getSession().getTransactionCoordinator() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( buffer.toString(), true ); + + // prepare parameters + int i = 1; + for ( ParameterRegistrationImplementor parameter : registeredParameters ) { + if ( parameter == null ) { + throw new QueryException( "Registered stored procedure parameters had gaps" ); + } + + parameter.prepare( statement, i ); + i += parameter.getSqlTypes().length; + } + + return new ProcedureResultImpl( this, statement ); + } + catch (SQLException e) { + throw getSession().getFactory().getSQLExceptionHelper().convert( + e, + "Error preparing CallableStatement", + getProcedureName() + ); + } + } + + + @Override + public Type[] getReturnTypes() throws HibernateException { + throw new NotYetImplementedException(); + } + + protected Set synchronizedQuerySpaces() { + if ( synchronizedQuerySpaces == null ) { + synchronizedQuerySpaces = new HashSet(); + } + return synchronizedQuerySpaces; + } + + @Override + @SuppressWarnings("unchecked") + public Set getSynchronizedQuerySpaces() { + if ( synchronizedQuerySpaces == null ) { + return Collections.emptySet(); + } + else { + return Collections.unmodifiableSet( synchronizedQuerySpaces ); + } + } + + @Override + public ProcedureCallImpl addSynchronizedQuerySpace(String querySpace) { + synchronizedQuerySpaces().add( querySpace ); + return this; + } + + @Override + public ProcedureCallImpl addSynchronizedEntityName(String entityName) { + addSynchronizedQuerySpaces( getSession().getFactory().getEntityPersister( entityName ) ); + return this; + } + + protected void addSynchronizedQuerySpaces(EntityPersister persister) { + synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) ); + } + + @Override + public ProcedureCallImpl addSynchronizedEntityClass(Class entityClass) { + addSynchronizedQuerySpaces( getSession().getFactory().getEntityPersister( entityClass.getName() ) ); + return this; + } + + @Override + public QueryParameters getQueryParameters() { + return buildQueryParametersObject(); + } + + public QueryParameters buildQueryParametersObject() { + QueryParameters qp = super.buildQueryParametersObject(); + // both of these are for documentation purposes, they are actually handled directly... + qp.setAutoDiscoverScalarTypes( true ); + qp.setCallable( true ); + return qp; + } + + public ParameterRegistrationImplementor[] collectRefCursorParameters() { + List refCursorParams = new ArrayList(); + for ( ParameterRegistrationImplementor param : registeredParameters ) { + if ( param.getMode() == ParameterMode.REF_CURSOR ) { + refCursorParams.add( param ); + } + } + return refCursorParams.toArray( new ParameterRegistrationImplementor[refCursorParams.size()] ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureResultImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureResultImpl.java new file mode 100644 index 0000000000..6b52b3f895 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureResultImpl.java @@ -0,0 +1,116 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.procedure.internal; + +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.JDBCException; +import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; +import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.procedure.ProcedureResult; +import org.hibernate.result.Return; +import org.hibernate.result.internal.ResultImpl; + +/** + * @author Steve Ebersole + */ +public class ProcedureResultImpl extends ResultImpl implements ProcedureResult { + private final ProcedureCallImpl procedureCall; + private final CallableStatement callableStatement; + + private final ParameterRegistrationImplementor[] refCursorParameters; + private int refCursorParamIndex = 0; + + ProcedureResultImpl(ProcedureCallImpl procedureCall, CallableStatement callableStatement) { + super( procedureCall, callableStatement ); + this.procedureCall = procedureCall; + this.callableStatement = callableStatement; + + this.refCursorParameters = procedureCall.collectRefCursorParameters(); + } + + @Override + public T getOutputParameterValue(ParameterRegistration parameterRegistration) { + return ( (ParameterRegistrationImplementor) parameterRegistration ).extract( callableStatement ); + } + + @Override + public Object getOutputParameterValue(String name) { + return procedureCall.getParameterRegistration( name ).extract( callableStatement ); + } + + @Override + public Object getOutputParameterValue(int position) { + return procedureCall.getParameterRegistration( position ).extract( callableStatement ); + } + + @Override + protected CurrentReturnDescriptor buildCurrentReturnDescriptor(boolean isResultSet, int updateCount) { + return new ProcedureCurrentReturnDescriptor( isResultSet, updateCount, refCursorParamIndex ); + } + + protected boolean hasMoreReturns(CurrentReturnDescriptor descriptor) { + return super.hasMoreReturns( descriptor ) + || ( (ProcedureCurrentReturnDescriptor) descriptor ).refCursorParamIndex < refCursorParameters.length; + } + + @Override + protected Return buildExtendedReturn(CurrentReturnDescriptor returnDescriptor) { + this.refCursorParamIndex++; + ResultSet resultSet; + int refCursorParamIndex = ( (ProcedureCurrentReturnDescriptor) returnDescriptor ).refCursorParamIndex; + ParameterRegistrationImplementor refCursorParam = refCursorParameters[refCursorParamIndex]; + if ( refCursorParam.getName() != null ) { + resultSet = procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .getResultSet( callableStatement, refCursorParam.getName() ); + } + else { + resultSet = procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .getResultSet( callableStatement, refCursorParam.getPosition() ); + } + return new ResultSetReturn( this, resultSet ); + } + + protected JDBCException convert(SQLException e, String message) { + return procedureCall.getSession().getFactory().getSQLExceptionHelper().convert( + e, + message, + procedureCall.getProcedureName() + ); + } + + protected static class ProcedureCurrentReturnDescriptor extends CurrentReturnDescriptor { + private final int refCursorParamIndex; + + private ProcedureCurrentReturnDescriptor(boolean isResultSet, int updateCount, int refCursorParamIndex) { + super( isResultSet, updateCount ); + this.refCursorParamIndex = refCursorParamIndex; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/package-info.java b/hibernate-core/src/main/java/org/hibernate/procedure/package-info.java new file mode 100644 index 0000000000..dea61abc24 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/package-info.java @@ -0,0 +1,37 @@ +package org.hibernate.procedure; + +/** + * Defines support for executing database stored procedures and functions and accessing its outputs. + *

+ * First a reference to {@link ProcedureCall} is obtained through one of the overloaded + * {@link org.hibernate.Session#createStoredProcedureCall} methods. The ProcedureCall reference is then used to "configure" + * the procedure call (set timeouts, etc) and to perform parameter registration. All procedure parameters that the + * application wants to use must be registered. For all IN and INOUT parameters, values can then be bound. + *

+ * At this point we are ready to execute the procedure call and start accessing the outputs. This is done by first + * calling the {@link ProcedureCall#getResult()} method. The underlying JDBC call is executed as needed. The pattern to + * access the returns is iterating through the outputs while {@link ProcedureResult#hasMoreReturns()} returns {@code true} and + * calling {@link ProcedureResult#getNextReturn()} during iteration: + * + * ProcedureCall call = session.createStoredProcedureCall( "some_procedure" ); + * ... + * ProcedureResult result = call.getResult(); + * while ( result.hasMoreReturns() ) { + * final Return rtn = result.getNextReturn(); + * if ( rtn.isResultSet() ) { + * handleResultSetReturn( (ResultSetReturn) rtn ); + * } + * else { + * handleUpdateCountReturn( (UpdateCountReturn) rtn ); + * } + * } + * + *

+ * Finally output parameters can be accessed using the overloaded {@link ProcedureResult#getOutputParameterValue} methods. + * For portability amongst databases, it is advised to access the output parameters after all returns have been + * processed. + * + * @see org.hibernate.Session#createStoredProcedureCall(String) + * @see org.hibernate.Session#createStoredProcedureCall(String, Class[]) + * @see org.hibernate.Session#createStoredProcedureCall(String, String...) + */ \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/result/NoMoreReturnsException.java b/hibernate-core/src/main/java/org/hibernate/result/NoMoreReturnsException.java new file mode 100644 index 0000000000..728a4a3e4e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/result/NoMoreReturnsException.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.result; + +import org.hibernate.HibernateException; + +/** + * @author Steve Ebersole + */ +public class NoMoreReturnsException extends HibernateException { + public NoMoreReturnsException(String message) { + super( message ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/result/Result.java b/hibernate-core/src/main/java/org/hibernate/result/Result.java new file mode 100644 index 0000000000..e42eeb8271 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/result/Result.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.result; + +/** + * Represents the result of executing a JDBC statement accounting for mixing of result sets and update counts hiding the + * complexity (IMO) of how this is exposed in the JDBC API. + * + * A result is made up of group of {@link Return} objects, each representing a single result set or update count. + * Conceptually, Result presents those Returns as an iterator. + * + * @author Steve Ebersole + */ +public interface Result { + /** + * Are there any more returns associated with this result? + * + * @return {@code true} means there are more returns available via {@link #getNextReturn()}; {@code false} + * indicates that calling {@link #getNextReturn()} will certainly result in an exception. + */ + public boolean hasMoreReturns(); + + /** + * Retrieve the next return. + * + * @return The next return. + */ + public Return getNextReturn() throws NoMoreReturnsException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureResultSetReturn.java b/hibernate-core/src/main/java/org/hibernate/result/ResultSetReturn.java similarity index 90% rename from hibernate-core/src/main/java/org/hibernate/StoredProcedureResultSetReturn.java rename to hibernate-core/src/main/java/org/hibernate/result/ResultSetReturn.java index 70b904c26b..9c520d8ada 100644 --- a/hibernate-core/src/main/java/org/hibernate/StoredProcedureResultSetReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/result/ResultSetReturn.java @@ -21,16 +21,16 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate; +package org.hibernate.result; import java.util.List; /** - * Models a stored procedure result that is a result set. + * Models a return that is a result set. * * @author Steve Ebersole */ -public interface StoredProcedureResultSetReturn extends StoredProcedureReturn { +public interface ResultSetReturn extends Return { /** * Consume the underlying {@link java.sql.ResultSet} and return the resulting List. * diff --git a/hibernate-core/src/main/java/org/hibernate/result/Return.java b/hibernate-core/src/main/java/org/hibernate/result/Return.java new file mode 100644 index 0000000000..93c75356ac --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/result/Return.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.result; + +/** + * Common contract for individual return objects which can be either results ({@link ResultSetReturn}) or update + * counts ({@link UpdateCountReturn}). + * + * @author Steve Ebersole + */ +public interface Return { + /** + * Determine if this return is a result (castable to {@link ResultSetReturn}). The alternative is that it is + * an update count (castable to {@link UpdateCountReturn}). + * + * @return {@code true} indicates that {@code this} can be safely cast to {@link ResultSetReturn}), other wise + * it can be cast to {@link UpdateCountReturn}. + */ + public boolean isResultSet(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/result/UpdateCountReturn.java b/hibernate-core/src/main/java/org/hibernate/result/UpdateCountReturn.java new file mode 100644 index 0000000000..5574b3f70f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/result/UpdateCountReturn.java @@ -0,0 +1,38 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.result; + +/** + * Models a return that is an update count (count of rows affected) + * + * @author Steve Ebersole + */ +public interface UpdateCountReturn extends Return { + /** + * Retrieve the associated update count + * + * @return The update count + */ + public int getUpdateCount(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/result/internal/ResultImpl.java similarity index 56% rename from hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureOutputsImpl.java rename to hibernate-core/src/main/java/org/hibernate/result/internal/ResultImpl.java index 7ece485945..fe405ec56f 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureOutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/result/internal/ResultImpl.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -21,9 +21,9 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.internal; +package org.hibernate.result.internal; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; @@ -32,51 +32,34 @@ import java.util.Map; import java.util.Set; import org.hibernate.JDBCException; -import org.hibernate.StoredProcedureOutputs; -import org.hibernate.StoredProcedureResultSetReturn; -import org.hibernate.StoredProcedureReturn; -import org.hibernate.StoredProcedureUpdateCountReturn; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.internal.StoredProcedureCallImpl.StoredProcedureParameterImplementor; import org.hibernate.loader.custom.CustomLoader; import org.hibernate.loader.custom.CustomQuery; -import org.hibernate.loader.custom.Return; import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor; -import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; +import org.hibernate.result.NoMoreReturnsException; +import org.hibernate.result.Result; +import org.hibernate.result.Return; +import org.hibernate.result.spi.ResultContext; /** * @author Steve Ebersole */ -public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { - private final StoredProcedureCallImpl procedureCall; - private final CallableStatement callableStatement; - - private final StoredProcedureParameterImplementor[] refCursorParameters; +public class ResultImpl implements Result { + private final ResultContext context; + private final PreparedStatement jdbcStatement; private final CustomLoaderExtension loader; private CurrentReturnDescriptor currentReturnDescriptor; private boolean executed = false; - private int refCursorParamIndex = 0; - StoredProcedureOutputsImpl(StoredProcedureCallImpl procedureCall, CallableStatement callableStatement) { - this.procedureCall = procedureCall; - this.callableStatement = callableStatement; + public ResultImpl(ResultContext context, PreparedStatement jdbcStatement) { + this.context = context; + this.jdbcStatement = jdbcStatement; - this.refCursorParameters = procedureCall.collectRefCursorParameters(); // For now... - this.loader = buildSpecializedCustomLoader( procedureCall ); - } - - @Override - public Object getOutputParameterValue(String name) { - return procedureCall.getRegisteredParameter( name ).extract( callableStatement ); - } - - @Override - public Object getOutputParameterValue(int position) { - return procedureCall.getRegisteredParameter( position ).extract( callableStatement ); + this.loader = buildSpecializedCustomLoader( context ); } @Override @@ -86,7 +69,7 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { if ( executed ) { try { - isResultSet = callableStatement.getMoreResults(); + isResultSet = jdbcStatement.getMoreResults(); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.getMoreResults" ); @@ -94,7 +77,7 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { } else { try { - isResultSet = callableStatement.execute(); + isResultSet = jdbcStatement.execute(); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.execute" ); @@ -105,27 +88,30 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { int updateCount = -1; if ( ! isResultSet ) { try { - updateCount = callableStatement.getUpdateCount(); + updateCount = jdbcStatement.getUpdateCount(); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.getUpdateCount" ); } } - currentReturnDescriptor = new CurrentReturnDescriptor( isResultSet, updateCount, refCursorParamIndex ); + currentReturnDescriptor = buildCurrentReturnDescriptor( isResultSet, updateCount ); } - return hasMoreResults( currentReturnDescriptor ); + return hasMoreReturns( currentReturnDescriptor ); } - private boolean hasMoreResults(CurrentReturnDescriptor descriptor) { + protected CurrentReturnDescriptor buildCurrentReturnDescriptor(boolean isResultSet, int updateCount) { + return new CurrentReturnDescriptor( isResultSet, updateCount ); + } + + protected boolean hasMoreReturns(CurrentReturnDescriptor descriptor) { return descriptor.isResultSet - || descriptor.updateCount >= 0 - || descriptor.refCursorParamIndex < refCursorParameters.length; + || descriptor.updateCount >= 0; } @Override - public StoredProcedureReturn getNextReturn() { + public Return getNextReturn() { if ( currentReturnDescriptor == null ) { if ( executed ) { throw new IllegalStateException( "Unexpected condition" ); @@ -135,8 +121,8 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { } } - if ( ! hasMoreResults( currentReturnDescriptor ) ) { - throw new IllegalStateException( "Results have been exhausted" ); + if ( ! hasMoreReturns( currentReturnDescriptor ) ) { + throw new NoMoreReturnsException( "Results have been exhausted" ); } CurrentReturnDescriptor copyReturnDescriptor = currentReturnDescriptor; @@ -144,7 +130,7 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { if ( copyReturnDescriptor.isResultSet ) { try { - return new ResultSetReturn( this, callableStatement.getResultSet() ); + return new ResultSetReturn( this, jdbcStatement.getResultSet() ); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.getResultSet" ); @@ -154,49 +140,37 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { return new UpdateCountReturn( this, copyReturnDescriptor.updateCount ); } else { - this.refCursorParamIndex++; - ResultSet resultSet; - int refCursorParamIndex = copyReturnDescriptor.refCursorParamIndex; - StoredProcedureParameterImplementor refCursorParam = refCursorParameters[refCursorParamIndex]; - if ( refCursorParam.getName() != null ) { - resultSet = procedureCall.session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .getResultSet( callableStatement, refCursorParam.getName() ); - } - else { - resultSet = procedureCall.session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .getResultSet( callableStatement, refCursorParam.getPosition() ); - } - return new ResultSetReturn( this, resultSet ); + return buildExtendedReturn( copyReturnDescriptor ); } } + protected Return buildExtendedReturn(CurrentReturnDescriptor copyReturnDescriptor) { + throw new NoMoreReturnsException( "Results have been exhausted" ); + } + protected JDBCException convert(SQLException e, String message) { - return procedureCall.session().getFactory().getSQLExceptionHelper().convert( + return context.getSession().getFactory().getSQLExceptionHelper().convert( e, message, - procedureCall.getProcedureName() + context.getSql() ); } - private static class CurrentReturnDescriptor { + protected static class CurrentReturnDescriptor { private final boolean isResultSet; private final int updateCount; - private final int refCursorParamIndex; - private CurrentReturnDescriptor(boolean isResultSet, int updateCount, int refCursorParamIndex) { + protected CurrentReturnDescriptor(boolean isResultSet, int updateCount) { this.isResultSet = isResultSet; this.updateCount = updateCount; - this.refCursorParamIndex = refCursorParamIndex; } } - private static class ResultSetReturn implements StoredProcedureResultSetReturn { - private final StoredProcedureOutputsImpl storedProcedureOutputs; + protected static class ResultSetReturn implements org.hibernate.result.ResultSetReturn { + private final ResultImpl storedProcedureOutputs; private final ResultSet resultSet; - public ResultSetReturn(StoredProcedureOutputsImpl storedProcedureOutputs, ResultSet resultSet) { + public ResultSetReturn(ResultImpl storedProcedureOutputs, ResultSet resultSet) { this.storedProcedureOutputs = storedProcedureOutputs; this.resultSet = resultSet; } @@ -229,12 +203,12 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { } } - private class UpdateCountReturn implements StoredProcedureUpdateCountReturn { - private final StoredProcedureOutputsImpl storedProcedureOutputs; + protected static class UpdateCountReturn implements org.hibernate.result.UpdateCountReturn { + private final ResultImpl result; private final int updateCount; - public UpdateCountReturn(StoredProcedureOutputsImpl storedProcedureOutputs, int updateCount) { - this.storedProcedureOutputs = storedProcedureOutputs; + public UpdateCountReturn(ResultImpl result, int updateCount) { + this.result = result; this.updateCount = updateCount; } @@ -249,23 +223,23 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { } } - private static CustomLoaderExtension buildSpecializedCustomLoader(final StoredProcedureCallImpl procedureCall) { + private static CustomLoaderExtension buildSpecializedCustomLoader(final ResultContext context) { final SQLQueryReturnProcessor processor = new SQLQueryReturnProcessor( - procedureCall.getQueryReturns(), - procedureCall.session().getFactory() + context.getQueryReturns(), + context.getSession().getFactory() ); processor.process(); - final List customReturns = processor.generateCustomReturns( false ); + final List customReturns = processor.generateCustomReturns( false ); CustomQuery customQuery = new CustomQuery() { @Override public String getSQL() { - return procedureCall.getProcedureName(); + return context.getSql(); } @Override public Set getQuerySpaces() { - return procedureCall.getSynchronizedQuerySpacesSet(); + return context.getSynchronizedQuerySpaces(); } @Override @@ -275,15 +249,15 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { } @Override - public List getCustomQueryReturns() { + public List getCustomQueryReturns() { return customReturns; } }; return new CustomLoaderExtension( customQuery, - procedureCall.buildQueryParametersObject(), - procedureCall.session() + context.getQueryParameters(), + context.getSession() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/result/package-info.java b/hibernate-core/src/main/java/org/hibernate/result/package-info.java new file mode 100644 index 0000000000..8aa9877fa3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/result/package-info.java @@ -0,0 +1,24 @@ +package org.hibernate.result; + +/** + * Defines support for dealing with database results, accounting for mixed result sets and update counts hiding the + * complexity (IMO) of how this is exposed in the JDBC API. + * + * {@link Result} represents the overall group of results. + * + * {@link Return} represents the mixed individual outcomes, which might be either a {@link ResultSetReturn} or + * a {@link UpdateCountReturn}. + * + * + * Result result = ...; + * while ( result.hasMoreReturns() ) { + * final Return rtn = result.getNextReturn(); + * if ( rtn.isResultSet() ) { + * handleResultSetReturn( (ResultSetReturn) rtn ); + * } + * else { + * handleUpdateCountReturn( (UpdateCountReturn) rtn ); + * } + * } + * + */ \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/result/spi/ResultContext.java b/hibernate-core/src/main/java/org/hibernate/result/spi/ResultContext.java new file mode 100644 index 0000000000..60a2600c76 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/result/spi/ResultContext.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.result.spi; + +import java.util.Set; + +import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionImplementor; + +/** + * @author Steve Ebersole + */ +public interface ResultContext { + public SessionImplementor getSession(); + public Set getSynchronizedQuerySpaces(); + + // for now... + // see Loader-redesign proposal + public String getSql(); + public QueryParameters getQueryParameters(); + public NativeSQLQueryReturn[] getQueryReturns(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JACCPermissions.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JACCPermissions.java index f83570ae55..31eb980f47 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JACCPermissions.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JACCPermissions.java @@ -84,7 +84,7 @@ public class JACCPermissions { PolicyContextActions PRIVILEGED = new PolicyContextActions() { private final PrivilegedExceptionAction exAction = new PrivilegedExceptionAction() { public Object run() throws Exception { - return (Subject) PolicyContext.getContext( SUBJECT_CONTEXT_KEY ); + return PolicyContext.getContext( SUBJECT_CONTEXT_KEY ); } }; diff --git a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java index d7b4b4355e..1cd6b3bfd3 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java +++ b/hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java @@ -28,6 +28,14 @@ import java.util.Collections; import java.util.List; import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +import org.hibernate.engine.jdbc.dialect.internal.DatabaseInfoDialectResolverInitiator; +import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator; +import org.hibernate.engine.transaction.internal.TransactionFactoryInitiator; +import org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator; +import org.hibernate.id.factory.internal.MutableIdentifierGeneratorFactoryInitiator; +import org.hibernate.persister.internal.PersisterClassResolverInitiator; +import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.engine.config.internal.ConfigurationServiceInitiator; import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator; @@ -70,6 +78,7 @@ public class StandardServiceInitiators { serviceInitiators.add( ConnectionProviderInitiator.INSTANCE ); serviceInitiators.add( MultiTenantConnectionProviderInitiator.INSTANCE ); + serviceInitiators.add( DatabaseInfoDialectResolverInitiator.INSTANCE ); serviceInitiators.add( DialectResolverInitiator.INSTANCE ); serviceInitiators.add( DialectFactoryInitiator.INSTANCE ); serviceInitiators.add( BatchBuilderInitiator.INSTANCE ); @@ -82,6 +91,7 @@ public class StandardServiceInitiators { serviceInitiators.add( MutableIdentifierGeneratorFactoryInitiator.INSTANCE); serviceInitiators.add( JtaPlatformInitiator.INSTANCE ); + serviceInitiators.add( JtaPlatformResolverInitiator.INSTANCE ); serviceInitiators.add( TransactionFactoryInitiator.INSTANCE ); serviceInitiators.add( SessionFactoryServiceRegistryFactoryInitiator.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java index 375199aac9..71895a88dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ForUpdateFragment.java @@ -39,6 +39,7 @@ import org.hibernate.internal.util.StringHelper; public class ForUpdateFragment { private final StringBuilder aliases = new StringBuilder(); private boolean isNowaitEnabled; + private boolean isSkipLockedEnabled; private final Dialect dialect; private LockMode lockMode; private LockOptions lockOptions; @@ -89,6 +90,10 @@ public class ForUpdateFragment { if ( upgradeType == LockMode.UPGRADE_NOWAIT ) { setNowaitEnabled( true ); } + + if ( upgradeType == LockMode.UPGRADE_SKIPLOCKED ) { + setSkipLockedEnabled( true ); + } } public ForUpdateFragment addTableAlias(String alias) { @@ -110,13 +115,25 @@ public class ForUpdateFragment { return ""; } // TODO: pass lockmode - return isNowaitEnabled ? - dialect.getForUpdateNowaitString( aliases.toString() ) : - dialect.getForUpdateString( aliases.toString() ); + if(isNowaitEnabled) { + return dialect.getForUpdateNowaitString( aliases.toString() ); + } + else if (isSkipLockedEnabled) { + return dialect.getForUpdateSkipLockedString( aliases.toString() ); + } + else { + return dialect.getForUpdateString( aliases.toString() ); + } } public ForUpdateFragment setNowaitEnabled(boolean nowait) { isNowaitEnabled = nowait; return this; } + + public ForUpdateFragment setSkipLockedEnabled(boolean skipLocked) { + isSkipLockedEnabled = skipLocked; + return this; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Template.java b/hibernate-core/src/main/java/org/hibernate/sql/Template.java index 9f062b9a72..0303dc6b9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Template.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Template.java @@ -119,7 +119,12 @@ public final class Template { @Deprecated @SuppressWarnings({ "JavaDoc" }) public static String renderWhereStringTemplate(String sqlWhereString, String placeholder, Dialect dialect) { - return renderWhereStringTemplate( sqlWhereString, placeholder, dialect, new SQLFunctionRegistry( dialect, java.util.Collections.EMPTY_MAP ) ); + return renderWhereStringTemplate( + sqlWhereString, + placeholder, + dialect, + new SQLFunctionRegistry( dialect, java.util.Collections.emptyMap() ) + ); } /** @@ -302,7 +307,7 @@ public final class Template { else if ( isNamedParameter(token) ) { result.append(token); } - else if ( isIdentifier(token, dialect) + else if ( isIdentifier(token) && !isFunctionOrKeyword(lcToken, nextToken, dialect , functionRegistry) ) { result.append(placeholder) .append('.') @@ -569,31 +574,32 @@ public final class Template { private final String trimSource; private TrimOperands(List operands) { - if ( operands.size() == 1 ) { + final int size = operands.size(); + if ( size == 1 ) { trimSpec = null; trimChar = null; from = null; trimSource = operands.get(0); } - else if ( operands.size() == 4 ) { + else if ( size == 4 ) { trimSpec = operands.get(0); trimChar = operands.get(1); from = operands.get(2); trimSource = operands.get(3); } else { - if ( operands.size() < 1 || operands.size() > 4 ) { - throw new HibernateException( "Unexpected number of trim function operands : " + operands.size() ); + if ( size < 1 || size > 4 ) { + throw new HibernateException( "Unexpected number of trim function operands : " + size ); } // trim-source will always be the last operand - trimSource = operands.get( operands.size() - 1 ); + trimSource = operands.get( size - 1 ); // ANSI SQL says that more than one operand means that the FROM is required - if ( ! "from".equals( operands.get( operands.size() - 2 ) ) ) { - throw new HibernateException( "Expecting FROM, found : " + operands.get( operands.size() - 2 ) ); + if ( ! "from".equals( operands.get( size - 2 ) ) ) { + throw new HibernateException( "Expecting FROM, found : " + operands.get( size - 2 ) ); } - from = operands.get( operands.size() - 2 ); + from = operands.get( size - 2 ); // trim-spec, if there is one will always be the first operand if ( "leading".equalsIgnoreCase( operands.get(0) ) @@ -604,7 +610,7 @@ public final class Template { } else { trimSpec = null; - if ( operands.size() - 2 == 0 ) { + if ( size - 2 == 0 ) { trimChar = null; } else { @@ -749,7 +755,7 @@ public final class Template { return ! function.hasParenthesesIfNoArguments(); } - private static boolean isIdentifier(String token, Dialect dialect) { + private static boolean isIdentifier(String token) { return token.charAt(0)=='`' || ( //allow any identifier quoted with backtick Character.isLetter( token.charAt(0) ) && //only recognizes identifiers beginning with a letter token.indexOf('.') < 0 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java index 84df345c60..9ddf2616e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java @@ -227,7 +227,7 @@ public class OrderByFragmentParser extends GeneratedOrderByFragmentParser { private String adjustTemplateReferences(String template) { int templateLength = template.length(); int startPos = template.indexOf( Template.TEMPLATE ); - while ( startPos < templateLength ) { + while ( startPos != -1 && startPos < templateLength ) { int dotPos = startPos + TEMPLATE_MARKER_LENGTH; // from here we need to seek the end of the qualified identifier @@ -245,7 +245,7 @@ public class OrderByFragmentParser extends GeneratedOrderByFragmentParser { columnReferences.add( columnReference ); // prep for the next seek - startPos = ( pos - TEMPLATE_MARKER_LENGTH ) + 1; + startPos = template.indexOf( Template.TEMPLATE, ( pos - TEMPLATE_MARKER_LENGTH ) + 1 ); templateLength = template.length(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java index 8544632c46..561b271565 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java @@ -26,6 +26,8 @@ package org.hibernate.sql.ordering.antlr; import antlr.collections.AST; import org.jboss.logging.Logger; +import org.hibernate.NullPrecedence; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.ast.util.ASTPrinter; import org.hibernate.internal.util.StringHelper; @@ -41,6 +43,12 @@ public class OrderByFragmentRenderer extends GeneratedOrderByFragmentRenderer { private static final Logger LOG = Logger.getLogger( OrderByFragmentRenderer.class.getName() ); private static final ASTPrinter printer = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class ); + private final SessionFactoryImplementor sessionFactory; + + public OrderByFragmentRenderer(SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; + } + @Override protected void out(AST ast) { out( ( ( Node ) ast ).getRenderableText() ); @@ -75,4 +83,10 @@ public class OrderByFragmentRenderer extends GeneratedOrderByFragmentRenderer { String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " "; LOG.trace( prefix + ruleName ); } + + @Override + protected String renderOrderByElement(String expression, String collation, String order, String nulls) { + final NullPrecedence nullPrecedence = NullPrecedence.parse( nulls, sessionFactory.getSettings().getDefaultNullPrecedence() ); + return sessionFactory.getDialect().renderOrderByElement( expression, collation, order, nullPrecedence ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java index ec88066bd1..5e5be00003 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java @@ -75,7 +75,7 @@ public class OrderByFragmentTranslator { } // Render the parsed tree to text. - OrderByFragmentRenderer renderer = new OrderByFragmentRenderer(); + OrderByFragmentRenderer renderer = new OrderByFragmentRenderer( context.getSessionFactory() ); try { renderer.orderByFragment( parser.getAST() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java index 9ba57f4204..3b117d9445 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java @@ -41,7 +41,7 @@ public class SessionStatisticsImpl implements SessionStatistics { } public int getEntityCount() { - return session.getPersistenceContext().getEntityEntries().size(); + return session.getPersistenceContext().getNumberOfManagedEntities(); } public int getCollectionCount() { @@ -60,7 +60,7 @@ public class SessionStatisticsImpl implements SessionStatistics { return new StringBuilder() .append("SessionStatistics[") .append("entity count=").append( getEntityCount() ) - .append("collection count=").append( getCollectionCount() ) + .append(",collection count=").append( getCollectionCount() ) .append(']') .toString(); } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java new file mode 100644 index 0000000000..1ef02dfa6a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java @@ -0,0 +1,192 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.tool.enhance; + +import javax.persistence.Entity; +import javax.persistence.Transient; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtField; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; + +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; + +/** + * Ant task for performing build-time enhancement of entities and component/embeddable classes. + *

+ * IMPL NOTE : currently makes numerous assumptions, the most "horrific" being that all entities are + * annotated @Entity which precludes {@code hbm.xml} mappings as well as complete {@code orm.xml} mappings. This is + * just a PoC though... + * + * @author Steve Ebersole + * + * @see org.hibernate.engine.spi.Managed + */ +public class EnhancementTask extends Task implements EnhancementContext { + private List filesets = new ArrayList(); + + // Enhancer also builds CtClass instances. Might make sense to share these (ClassPool). + private final ClassPool classPool = new ClassPool( false ); + private final Enhancer enhancer = new Enhancer( this ); + + public void addFileset(FileSet set) { + this.filesets.add( set ); + } + + @Override + public void execute() throws BuildException { + log( "Starting Hibernate EnhancementTask execution", Project.MSG_INFO ); + + // we use the CtClass stuff here just as a simple vehicle for obtaining low level information about + // the class(es) contained in a file while still maintaining easy access to the underlying byte[] + final Project project = getProject(); + + for ( FileSet fileSet : filesets ) { + final File fileSetBaseDir = fileSet.getDir( project ); + final DirectoryScanner directoryScanner = fileSet.getDirectoryScanner( project ); + for ( String relativeIncludedFileName : directoryScanner.getIncludedFiles() ) { + final File javaClassFile = new File( fileSetBaseDir, relativeIncludedFileName ); + if ( ! javaClassFile.exists() ) { + continue; + } + + processClassFile( javaClassFile ); + } + } + } + + private void processClassFile(File javaClassFile) { + try { + final CtClass ctClass = classPool.makeClass( new FileInputStream( javaClassFile ) ); + if ( ! shouldInclude( ctClass ) ) { + return; + } + + final byte[] enhancedBytecode; + try { + enhancedBytecode = enhancer.enhance( ctClass.getName(), ctClass.toBytecode() ); + } + catch (Exception e) { + log( "Unable to enhance class [" + ctClass.getName() + "]", e, Project.MSG_WARN ); + return; + } + + if ( javaClassFile.delete() ) { + if ( ! javaClassFile.createNewFile() ) { + log( "Unable to recreate class file [" + ctClass.getName() + "]", Project.MSG_INFO ); + } + } + else { + log( "Unable to delete class file [" + ctClass.getName() + "]", Project.MSG_INFO ); + } + + FileOutputStream outputStream = new FileOutputStream( javaClassFile, false ); + try { + outputStream.write( enhancedBytecode ); + outputStream.flush(); + } + finally { + try { + outputStream.close(); + } + catch ( IOException ignore) { + } + } + } + catch (FileNotFoundException ignore) { + // should not ever happen because of explicit checks + } + catch (IOException e) { + throw new BuildException( + String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), + e + ); + } + } + + private boolean shouldInclude(CtClass ctClass) { + // we currently only handle entity enhancement + return ! ctClass.hasAnnotation( Entity.class ); + } + + + // EnhancementContext impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public ClassLoader getLoadingClassLoader() { + return getClass().getClassLoader(); + } + + @Override + public boolean isEntityClass(CtClass classDescriptor) { + // currently we only call enhance on the classes with @Entity, so here we always return true + return true; + } + + @Override + public boolean isCompositeClass(CtClass classDescriptor) { + return false; + } + + @Override + public boolean doDirtyCheckingInline(CtClass classDescriptor) { + return false; + } + + @Override + public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { + return true; + } + + @Override + public boolean isLazyLoadable(CtField field) { + return true; + } + + @Override + public boolean isPersistentField(CtField ctField) { + // current check is to look for @Transient + return ! ctField.hasAnnotation( Transient.class ); + } + + @Override + public CtField[] order(CtField[] persistentFields) { + // for now... + return persistentFields; + // eventually needs to consult the Hibernate metamodel for proper ordering + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/DatabaseMetadata.java b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/DatabaseMetadata.java index 29f24350e8..4fcadebe3e 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/DatabaseMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/hbm2ddl/DatabaseMetadata.java @@ -122,7 +122,7 @@ public class DatabaseMetadata { } finally { - if (rs!=null) rs.close(); + rs.close(); } } catch (SQLException sqlException) { @@ -153,8 +153,8 @@ public class DatabaseMetadata { } } finally { - if (rs!=null) rs.close(); - if (statement!=null) statement.close(); + rs.close(); + statement.close(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java index 35f473f7a3..2efb1bdf90 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java @@ -307,6 +307,9 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer { if ( entityMetamodel.getIdentifierProperty().isEmbedded() ) { id = entity; } + else if ( HibernateProxy.class.isInstance( entity ) ) { + id = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier(); + } else { if ( idGetter == null ) { if ( !hasIdentifierMapper) { @@ -610,13 +613,13 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer { ) ); } - final Object baseValue = getPropertyValue( entity, index.intValue() ); + final Object baseValue = getPropertyValue( entity, index ); if ( loc > 0 ) { if ( baseValue == null ) { return null; } return getComponentValue( - (ComponentType) entityMetamodel.getPropertyTypes()[index.intValue()], + (ComponentType) entityMetamodel.getPropertyTypes()[index], baseValue, propertyPath.substring(loc+1) ); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 5e013f66ea..a23b55e847 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -788,7 +788,7 @@ public class EntityMetamodel implements Serializable { if ( index == null ) { throw new HibernateException("Unable to resolve property: " + propertyName); } - return index.intValue(); + return index; } public Integer getPropertyIndexOrNull(String propertyName) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java index cae42dfc10..05e91c1d3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java @@ -64,6 +64,8 @@ public class BasicTypeRegistry implements Serializable { register( BigIntegerType.INSTANCE ); register( StringType.INSTANCE ); + register( StringNVarcharType.INSTANCE ); + register( CharacterNCharType.INSTANCE ); register( UrlType.INSTANCE ); register( DateType.INSTANCE ); @@ -87,10 +89,13 @@ public class BasicTypeRegistry implements Serializable { register( CharArrayType.INSTANCE ); register( CharacterArrayType.INSTANCE ); register( TextType.INSTANCE ); + register( NTextType.INSTANCE ); register( BlobType.INSTANCE ); register( MaterializedBlobType.INSTANCE ); register( ClobType.INSTANCE ); + register( NClobType.INSTANCE ); register( MaterializedClobType.INSTANCE ); + register( MaterializedNClobType.INSTANCE ); register( SerializableType.INSTANCE ); register( PrimitiveCharacterArrayClobType.INSTANCE ); register( WrappedMaterializedBlobType.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/CharacterArrayNClobType.java b/hibernate-core/src/main/java/org/hibernate/type/CharacterArrayNClobType.java new file mode 100644 index 0000000000..43d4cd8ed5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/CharacterArrayNClobType.java @@ -0,0 +1,49 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type; + +import org.hibernate.type.descriptor.java.CharacterArrayTypeDescriptor; +import org.hibernate.type.descriptor.sql.NClobTypeDescriptor; + +/** + * A type that maps between {@link java.sql.Types#NCLOB NCLOB} and {@link Character Character[]} + *

+ * Essentially a {@link org.hibernate.type.MaterializedNClobType} but represented as a Character[] in Java rather than String. + * + * @author Emmanuel Bernard + * @author Steve Ebersole + */ +public class CharacterArrayNClobType extends AbstractSingleColumnStandardBasicType { + public static final CharacterArrayNClobType INSTANCE = new CharacterArrayNClobType(); + + public CharacterArrayNClobType() { + super( NClobTypeDescriptor.DEFAULT, CharacterArrayTypeDescriptor.INSTANCE ); + } + + public String getName() { + // todo name these annotation types for addition to the registry + return null; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/CharacterNCharType.java b/hibernate-core/src/main/java/org/hibernate/type/CharacterNCharType.java new file mode 100644 index 0000000000..3f0bf31374 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/CharacterNCharType.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type; + +import java.io.Serializable; + +import org.hibernate.dialect.Dialect; +import org.hibernate.type.descriptor.java.CharacterTypeDescriptor; +import org.hibernate.type.descriptor.sql.NCharTypeDescriptor; + +/** + * A type that maps between {@link java.sql.Types#NCHAR NCHAR(1)} and {@link Character} + * + * @author Gavin King + * @author Steve Ebersole + */ +public class CharacterNCharType + extends AbstractSingleColumnStandardBasicType + implements PrimitiveType, DiscriminatorType { + + public static final CharacterNCharType INSTANCE = new CharacterNCharType(); + + public CharacterNCharType() { + super( NCharTypeDescriptor.INSTANCE, CharacterTypeDescriptor.INSTANCE ); + } + + public String getName() { + return "ncharacter"; + } + + public Serializable getDefaultValue() { + throw new UnsupportedOperationException( "not a valid id type" ); + } + + public Class getPrimitiveClass() { + return char.class; + } + + public String objectToSQLString(Character value, Dialect dialect) { + return '\'' + toString( value ) + '\''; + } + + public Character stringToObject(String xml) { + return fromString( xml ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index 3a10188096..83d12f7cff 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -379,7 +379,7 @@ public abstract class CollectionType extends AbstractType implements Association // TODO: Fix this so it will work for non-POJO entity mode Type keyType = getPersister( session ).getKeyType(); if ( !keyType.getReturnedClass().isInstance( id ) ) { - id = (Serializable) keyType.semiResolve( + id = keyType.semiResolve( entityEntry.getLoadedValue( foreignKeyPropertyName ), session, owner @@ -525,15 +525,13 @@ public abstract class CollectionType extends AbstractType implements Association // on the target because we simply do not know... if ( original instanceof PersistentCollection ) { if ( result instanceof PersistentCollection ) { - if ( ! ( ( PersistentCollection ) original ).isDirty() ) { - ( ( PersistentCollection ) result ).clearDirty(); - } + final PersistentCollection originalPersistentCollection = (PersistentCollection) original; + final PersistentCollection resultPersistentCollection = (PersistentCollection) result; - if ( elemType instanceof AssociationType ) { - preserveSnapshot( (PersistentCollection) original, - (PersistentCollection) result, - (AssociationType) elemType, owner, copyCache, - session ); + preserveSnapshot( originalPersistentCollection, resultPersistentCollection, elemType, owner, copyCache, session ); + + if ( ! originalPersistentCollection.isDirty() ) { + resultPersistentCollection.clearDirty(); } } } @@ -541,9 +539,13 @@ public abstract class CollectionType extends AbstractType implements Association return result; } - private void preserveSnapshot(PersistentCollection original, - PersistentCollection result, AssociationType elemType, - Object owner, Map copyCache, SessionImplementor session) { + private void preserveSnapshot( + PersistentCollection original, + PersistentCollection result, + Type elemType, + Object owner, + Map copyCache, + SessionImplementor session) { Serializable originalSnapshot = original.getStoredSnapshot(); Serializable resultSnapshot = result.getStoredSnapshot(); Serializable targetSnapshot; @@ -552,39 +554,35 @@ public abstract class CollectionType extends AbstractType implements Association targetSnapshot = new ArrayList( ( (List) originalSnapshot ).size() ); for ( Object obj : (List) originalSnapshot ) { - ( (List) targetSnapshot ).add( elemType.replace( - obj, null, session, owner, copyCache ) ); + ( (List) targetSnapshot ).add( elemType.replace( obj, null, session, owner, copyCache ) ); } } else if ( originalSnapshot instanceof Map ) { if ( originalSnapshot instanceof SortedMap ) { - targetSnapshot = new TreeMap( - ( (SortedMap) originalSnapshot ).comparator() ); + targetSnapshot = new TreeMap( ( (SortedMap) originalSnapshot ).comparator() ); } else { targetSnapshot = new HashMap( - CollectionHelper.determineProperSizing( - ( (Map) originalSnapshot ).size() ), - CollectionHelper.LOAD_FACTOR ); + CollectionHelper.determineProperSizing( ( (Map) originalSnapshot ).size() ), + CollectionHelper.LOAD_FACTOR + ); } - for ( Map.Entry entry : ( - (Map) originalSnapshot ).entrySet() ) { + for ( Map.Entry entry : ( (Map) originalSnapshot ).entrySet() ) { Object key = entry.getKey(); Object value = entry.getValue(); - Object resultSnapshotValue = ( resultSnapshot == null ) ? null + Object resultSnapshotValue = ( resultSnapshot == null ) + ? null : ( (Map) resultSnapshot ).get( key ); + Object newValue = elemType.replace( value, resultSnapshotValue, session, owner, copyCache ); + if ( key == value ) { - Object newValue = elemType.replace( value, - resultSnapshotValue, session, owner, copyCache ); ( (Map) targetSnapshot ).put( newValue, newValue ); } else { - Object newValue = elemType.replace( value, - resultSnapshotValue, session, owner, copyCache ); ( (Map) targetSnapshot ).put( key, newValue ); } @@ -594,8 +592,7 @@ public abstract class CollectionType extends AbstractType implements Association else if ( originalSnapshot instanceof Object[] ) { Object[] arr = (Object[]) originalSnapshot; for ( int i = 0; i < arr.length; i++ ) { - arr[i] = elemType.replace( - arr[i], null, session, owner, copyCache ); + arr[i] = elemType.replace( arr[i], null, session, owner, copyCache ); } targetSnapshot = originalSnapshot; @@ -606,8 +603,7 @@ public abstract class CollectionType extends AbstractType implements Association } - CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( - result ); + CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( result ); if ( ce != null ) { ce.resetStoredSnapshot( result, targetSnapshot ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java index bc605f9e59..62fa6c37fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java @@ -102,7 +102,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced public ComponentTuplizer getComponentTuplizer() { return componentTuplizer; } - + @Override public int getColumnSpan(Mapping mapping) throws MappingException { int span = 0; for ( int i = 0; i < propertySpan; i++ ) { @@ -110,7 +110,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced } return span; } - + @Override public int[] sqlTypes(Mapping mapping) throws MappingException { //Not called at runtime so doesn't matter if its slow :) int[] sqlTypes = new int[getColumnSpan( mapping )]; diff --git a/hibernate-core/src/main/java/org/hibernate/type/DbTimestampType.java b/hibernate-core/src/main/java/org/hibernate/type/DbTimestampType.java index 08f707df13..b2fb77a765 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/DbTimestampType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/DbTimestampType.java @@ -30,11 +30,10 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.util.Date; -import org.jboss.logging.Logger; - import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.CoreMessageLogger; +import org.jboss.logging.Logger; /** * dbtimestamp: An extension of {@link TimestampType} which @@ -91,7 +90,7 @@ public class DbTimestampType extends TimestampType { .getJdbcCoordinator() .getStatementPreparer() .prepareStatement( timestampSelectString, false ); - ResultSet rs = ps.executeQuery(); + ResultSet rs = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); rs.next(); Timestamp ts = rs.getTimestamp( 1 ); if ( LOG.isTraceEnabled() ) { @@ -108,12 +107,7 @@ public class DbTimestampType extends TimestampType { } finally { if ( ps != null ) { - try { - ps.close(); - } - catch( SQLException sqle ) { - LOG.unableToCleanUpPreparedStatement( sqle ); - } + session.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } } @@ -126,7 +120,7 @@ public class DbTimestampType extends TimestampType { .getStatementPreparer() .prepareStatement( callString, true ); cs.registerOutParameter( 1, java.sql.Types.TIMESTAMP ); - cs.execute(); + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().execute( cs ); Timestamp ts = cs.getTimestamp( 1 ); if ( LOG.isTraceEnabled() ) { LOG.tracev( "Current timestamp retreived from db : {0} (nanos={1}, time={2})", ts, ts.getNanos(), ts.getTime() ); @@ -142,12 +136,7 @@ public class DbTimestampType extends TimestampType { } finally { if ( cs != null ) { - try { - cs.close(); - } - catch( SQLException sqle ) { - LOG.unableToCleanUpCallableStatement( sqle ); - } + session.getTransactionCoordinator().getJdbcCoordinator().release( cs ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java index 939ab6105a..02d0183f04 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java @@ -37,12 +37,13 @@ import org.jboss.logging.Logger; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.util.ReflectHelper; -import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.usertype.DynamicParameterizedType; import org.hibernate.usertype.EnhancedUserType; +import org.hibernate.usertype.LoggableUserType; /** * Value type mapper for enumerations. @@ -67,12 +68,9 @@ import org.hibernate.usertype.EnhancedUserType; * @author Steve Ebersole */ @SuppressWarnings("unchecked") -public class EnumType implements EnhancedUserType, DynamicParameterizedType, Serializable, StringRepresentableType { - private static final Logger LOG = Logger.getLogger( EnumType.class.getName() ); - /** - * @deprecated use {@link DynamicParameterizedType#RETURNED_CLASS} instead. - */ - @Deprecated +public class EnumType implements EnhancedUserType, DynamicParameterizedType,LoggableUserType, Serializable { + private static final Logger LOG = Logger.getLogger( EnumType.class.getName() ); + public static final String ENUM = "enumClass"; public static final String NAMED = "useNamed"; public static final String TYPE = "type"; @@ -236,9 +234,6 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType, Ser } else { String enumClassName = (String) parameters.get( ENUM ); - if( StringHelper.isEmpty(enumClassName)){ - enumClassName = (String)parameters.get( DynamicParameterizedType.RETURNED_CLASS ); - } try { enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class ); } @@ -311,13 +306,11 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType, Ser } @Override - public String toString(Object value) throws HibernateException { - return enumValueMapper.toString( ( Enum) value ); - } - - @Override - public Object fromStringValue(String string) throws HibernateException { - return enumValueMapper.fromString( string ); + public String toLoggableString(Object value, SessionFactoryImplementor factory) { + if ( enumValueMapper != null ) { + return enumValueMapper.toXMLString( (Enum) value ); + } + return value.toString(); } private static interface EnumValueMapper extends Serializable { @@ -326,8 +319,8 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType, Ser public void setValue(PreparedStatement st, Enum value, int index) throws SQLException; public String objectToSQLString(Enum value); - public String toString(Enum value); - public Enum fromString(String xml); + public String toXMLString(Enum value); + public Enum fromXMLString(String xml); } public abstract class EnumValueMapperSupport implements EnumValueMapper { @@ -404,16 +397,16 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType, Ser @Override public String objectToSQLString(Enum value) { - return toString( value ); + return toXMLString( value ); } @Override - public String toString(Enum value) { + public String toXMLString(Enum value) { return Integer.toString( value.ordinal() ); } @Override - public Enum fromString(String xml) { + public Enum fromXMLString(String xml) { return fromOrdinal( Integer.parseInt( xml ) ); } @@ -464,16 +457,16 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType, Ser @Override public String objectToSQLString(Enum value) { - return '\'' + toString( value ) + '\''; + return '\'' + toXMLString( value ) + '\''; } @Override - public String toString(Enum value) { + public String toXMLString(Enum value) { return value.name(); } @Override - public Enum fromString(String xml) { + public Enum fromXMLString(String xml) { return fromName( xml ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/IntegerType.java b/hibernate-core/src/main/java/org/hibernate/type/IntegerType.java index a3b9f5a31a..5a83d9b298 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/IntegerType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/IntegerType.java @@ -79,7 +79,7 @@ public class IntegerType extends AbstractSingleColumnStandardBasicType @SuppressWarnings({ "UnnecessaryBoxing", "UnnecessaryUnboxing" }) public Integer next(Integer current, SessionImplementor session) { - return Integer.valueOf( current.intValue() + 1 ); + return current+1; } public Comparator getComparator() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/MaterializedNClobType.java b/hibernate-core/src/main/java/org/hibernate/type/MaterializedNClobType.java new file mode 100644 index 0000000000..4a6de4ecd0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/MaterializedNClobType.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type; + +import org.hibernate.type.descriptor.java.StringTypeDescriptor; +import org.hibernate.type.descriptor.sql.NClobTypeDescriptor; + +/** + * A type that maps between {@link java.sql.Types#CLOB CLOB} and {@link String} + * + * @author Gavin King + * @author Gail Badner + * @author Steve Ebersole + */ +public class MaterializedNClobType extends AbstractSingleColumnStandardBasicType { + public static final MaterializedNClobType INSTANCE = new MaterializedNClobType(); + + public MaterializedNClobType() { + super( NClobTypeDescriptor.DEFAULT, StringTypeDescriptor.INSTANCE ); + } + + public String getName() { + return "materialized_nclob"; + } +} \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/BasicStatementProxyHandler.java b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java similarity index 52% rename from hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/BasicStatementProxyHandler.java rename to hibernate-core/src/main/java/org/hibernate/type/NClobType.java index 39a7e65fba..979895950c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/BasicStatementProxyHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -21,35 +21,38 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.Method; -import java.sql.Connection; -import java.sql.Statement; +package org.hibernate.type; + +import java.sql.NClob; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.descriptor.java.NClobTypeDescriptor; /** - * Invocation handler for {@link Statement} proxies + * A type that maps between {@link java.sql.Types#CLOB CLOB} and {@link java.sql.Clob} * + * @author Gavin King * @author Steve Ebersole */ -public class BasicStatementProxyHandler extends AbstractStatementProxyHandler { - public BasicStatementProxyHandler( - Statement statement, - ConnectionProxyHandler connectionProxyHandler, - Connection connectionProxy) { - super( statement, connectionProxyHandler, connectionProxy ); +public class NClobType extends AbstractSingleColumnStandardBasicType { + public static final NClobType INSTANCE = new NClobType(); + + public NClobType() { + super( org.hibernate.type.descriptor.sql.NClobTypeDescriptor.DEFAULT, NClobTypeDescriptor.INSTANCE ); } - protected void beginningInvocationHandling(Method method, Object[] args) { - if ( isExecution( method ) ) { - getJdbcServices().getSqlStatementLogger().logStatement( ( String ) args[0] ); - } + public String getName() { + return "nclob"; } - private boolean isExecution(Method method) { - String methodName = method.getName(); - return "execute".equals( methodName ) - || "executeQuery".equals( methodName ) - || "executeUpdate".equals( methodName ); + @Override + protected boolean registerUnderJavaType() { + return true; } + + @Override + protected NClob getReplacement(NClob original, NClob target, SessionImplementor session) { + return session.getFactory().getDialect().getLobMergeStrategy().mergeNClob( original, target, session ); + } + } - diff --git a/hibernate-core/src/main/java/org/hibernate/type/NTextType.java b/hibernate-core/src/main/java/org/hibernate/type/NTextType.java new file mode 100644 index 0000000000..402876ff3d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/NTextType.java @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type; + +import org.hibernate.type.descriptor.java.StringTypeDescriptor; +import org.hibernate.type.descriptor.sql.LongNVarcharTypeDescriptor; + +/** + * A type that maps between {@link java.sql.Types#LONGNVARCHAR LONGNVARCHAR} and {@link String} + * + * @author Gavin King, + * @author Bertrand Renuart + * @author Steve Ebersole + */ +public class NTextType extends AbstractSingleColumnStandardBasicType { + public static final NTextType INSTANCE = new NTextType(); + + public NTextType() { + super( LongNVarcharTypeDescriptor.INSTANCE, StringTypeDescriptor.INSTANCE ); + } + + public String getName() { + return "ntext"; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayNClobType.java b/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayNClobType.java new file mode 100644 index 0000000000..031ea2c7f3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/PrimitiveCharacterArrayNClobType.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type; + +import org.hibernate.type.descriptor.java.PrimitiveCharacterArrayTypeDescriptor; +import org.hibernate.type.descriptor.sql.NClobTypeDescriptor; + +/** + * Map a char[] to a NClob + * + * @author Emmanuel Bernard + */ +public class PrimitiveCharacterArrayNClobType extends AbstractSingleColumnStandardBasicType { + public static final CharacterArrayClobType INSTANCE = new CharacterArrayClobType(); + + public PrimitiveCharacterArrayNClobType() { + super( NClobTypeDescriptor.DEFAULT, PrimitiveCharacterArrayTypeDescriptor.INSTANCE ); + } + + public String getName() { + // todo name these annotation types for addition to the registry + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java index 4b5927efe2..e853fba1a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java +++ b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java @@ -294,6 +294,15 @@ public class StandardBasicTypes { */ public static final TextType TEXT = TextType.INSTANCE; + /** + * The standard Hibernate type for mapping {@link String} to JDBC {@link java.sql.Types#LONGNVARCHAR LONGNVARCHAR}. + *

+ * Similar to a {@link #MATERIALIZED_NCLOB} + * + * @see NTextType + */ + public static final NTextType NTEXT = NTextType.INSTANCE; + /** * The standard Hibernate type for mapping {@link java.sql.Clob} to JDBC {@link java.sql.Types#CLOB CLOB}. * @@ -302,6 +311,14 @@ public class StandardBasicTypes { */ public static final ClobType CLOB = ClobType.INSTANCE; + /** + * The standard Hibernate type for mapping {@link java.sql.NClob} to JDBC {@link java.sql.Types#NCLOB NCLOB}. + * + * @see NClobType + * @see #MATERIALIZED_NCLOB + */ + public static final NClobType NCLOB = NClobType.INSTANCE; + /** * The standard Hibernate type for mapping {@link String} to JDBC {@link java.sql.Types#CLOB CLOB}. * @@ -311,6 +328,15 @@ public class StandardBasicTypes { */ public static final MaterializedClobType MATERIALIZED_CLOB = MaterializedClobType.INSTANCE; + /** + * The standard Hibernate type for mapping {@link String} to JDBC {@link java.sql.Types#NCLOB NCLOB}. + * + * @see MaterializedNClobType + * @see #MATERIALIZED_CLOB + * @see #NTEXT + */ + public static final MaterializedNClobType MATERIALIZED_NCLOB = MaterializedNClobType.INSTANCE; + /** * The standard Hibernate type for mapping {@link java.io.Serializable} to JDBC {@link java.sql.Types#VARBINARY VARBINARY}. *

diff --git a/hibernate-core/src/main/java/org/hibernate/type/StringNVarcharType.java b/hibernate-core/src/main/java/org/hibernate/type/StringNVarcharType.java new file mode 100644 index 0000000000..c97b52be54 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/StringNVarcharType.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type; + +import org.hibernate.dialect.Dialect; +import org.hibernate.type.descriptor.java.StringTypeDescriptor; +import org.hibernate.type.descriptor.sql.NVarcharTypeDescriptor; + +/** + * A type that maps between {@link java.sql.Types#VARCHAR VARCHAR} and {@link String} + * + * @author Gavin King + * @author Steve Ebersole + */ +public class StringNVarcharType + extends AbstractSingleColumnStandardBasicType + implements DiscriminatorType { + + public static final StringNVarcharType INSTANCE = new StringNVarcharType(); + + public StringNVarcharType() { + super( NVarcharTypeDescriptor.INSTANCE, StringTypeDescriptor.INSTANCE ); + } + + public String getName() { + return "nstring"; + } + + @Override + protected boolean registerUnderJavaType() { + return false; + } + + public String objectToSQLString(String value, Dialect dialect) throws Exception { + return '\'' + value + '\''; + } + + public String stringToObject(String xml) throws Exception { + return xml; + } + + public String toString(String value) { + return value; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java index 97a46c4e91..97bea4df87 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobTypeDescriptor.java @@ -23,14 +23,19 @@ */ package org.hibernate.type.descriptor.java; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; import java.io.Serializable; import java.sql.Clob; +import java.sql.SQLException; import java.util.Comparator; +import org.hibernate.HibernateException; +import org.hibernate.engine.jdbc.CharacterStream; import org.hibernate.engine.jdbc.ClobImplementer; import org.hibernate.engine.jdbc.ClobProxy; import org.hibernate.engine.jdbc.WrappedClob; -import org.hibernate.engine.jdbc.CharacterStream; import org.hibernate.engine.jdbc.internal.CharacterStreamImpl; import org.hibernate.type.descriptor.WrapperOptions; @@ -95,29 +100,33 @@ public class ClobTypeDescriptor extends AbstractTypeDescriptor { @SuppressWarnings({ "unchecked" }) public X unwrap(final Clob value, Class type, WrapperOptions options) { - if ( ! ( Clob.class.isAssignableFrom( type ) || CharacterStream.class.isAssignableFrom( type ) ) ) { - throw unknownUnwrap( type ); - } - if ( value == null ) { return null; } - if ( CharacterStream.class.isAssignableFrom( type ) ) { - if ( ClobImplementer.class.isInstance( value ) ) { - // if the incoming Clob is a wrapper, just pass along its CharacterStream - return (X) ( (ClobImplementer) value ).getUnderlyingStream(); + try { + if ( CharacterStream.class.isAssignableFrom( type ) ) { + if ( ClobImplementer.class.isInstance( value ) ) { + // if the incoming Clob is a wrapper, just pass along its CharacterStream + return (X) ( (ClobImplementer) value ).getUnderlyingStream(); + } + else { + // otherwise we need to build a CharacterStream... + return (X) new CharacterStreamImpl( DataHelper.extractString( value.getCharacterStream() ) ); + } } - else { - // otherwise we need to build one... - return (X) new CharacterStreamImpl( DataHelper.extractString( value ) ); + else if (Clob.class.isAssignableFrom( type )) { + final Clob clob = WrappedClob.class.isInstance( value ) + ? ( (WrappedClob) value ).getWrappedClob() + : value; + return (X) clob; } } - - final Clob clob = WrappedClob.class.isInstance( value ) - ? ( (WrappedClob) value ).getWrappedClob() - : value; - return (X) clob; + catch ( SQLException e ) { + throw new HibernateException( "Unable to access clob stream", e ); + } + + throw unknownUnwrap( type ); } public Clob wrap(X value, WrapperOptions options) { @@ -125,10 +134,16 @@ public class ClobTypeDescriptor extends AbstractTypeDescriptor { return null; } - if ( ! Clob.class.isAssignableFrom( value.getClass() ) ) { - throw unknownWrap( value.getClass() ); + // Support multiple return types from + // org.hibernate.type.descriptor.sql.ClobTypeDescriptor + if ( Clob.class.isAssignableFrom( value.getClass() ) ) { + return options.getLobCreator().wrap( (Clob) value ); + } + else if ( Reader.class.isAssignableFrom( value.getClass() ) ) { + Reader reader = (Reader) value; + return options.getLobCreator().createClob( DataHelper.extractString( reader ) ); } - return options.getLobCreator().wrap( (Clob) value ); + throw unknownWrap( value.getClass() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java index d405c71cd4..a3947e91f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java @@ -44,7 +44,7 @@ public abstract class MutableMutabilityPlan implements MutabilityPlan { @Override @SuppressWarnings({ "unchecked" }) public T assemble(Serializable cached) { - return (T) deepCopy( (T) cached ); + return deepCopy( (T) cached ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobTypeDescriptor.java new file mode 100644 index 0000000000..2e587e9c9a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobTypeDescriptor.java @@ -0,0 +1,147 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.java; + +import java.io.Reader; +import java.io.Serializable; +import java.sql.NClob; +import java.sql.SQLException; +import java.util.Comparator; + +import org.hibernate.HibernateException; +import org.hibernate.engine.jdbc.CharacterStream; +import org.hibernate.engine.jdbc.NClobImplementer; +import org.hibernate.engine.jdbc.NClobProxy; +import org.hibernate.engine.jdbc.WrappedNClob; +import org.hibernate.engine.jdbc.internal.CharacterStreamImpl; +import org.hibernate.type.descriptor.WrapperOptions; + +/** + * Descriptor for {@link java.sql.NClob} handling. + *

+ * Note, {@link java.sql.NClob nclobs} really are mutable (their internal state can in fact be mutated). We simply + * treat them as immutable because we cannot properly check them for changes nor deep copy them. + * + * @author Steve Ebersole + */ +public class NClobTypeDescriptor extends AbstractTypeDescriptor { + public static final NClobTypeDescriptor INSTANCE = new NClobTypeDescriptor(); + + public static class NClobMutabilityPlan implements MutabilityPlan { + public static final NClobMutabilityPlan INSTANCE = new NClobMutabilityPlan(); + + public boolean isMutable() { + return false; + } + + public NClob deepCopy(NClob value) { + return value; + } + + public Serializable disassemble(NClob value) { + throw new UnsupportedOperationException( "Clobs are not cacheable" ); + } + + public NClob assemble(Serializable cached) { + throw new UnsupportedOperationException( "Clobs are not cacheable" ); + } + } + + public NClobTypeDescriptor() { + super( NClob.class, NClobMutabilityPlan.INSTANCE ); + } + + public String toString(NClob value) { + return DataHelper.extractString( value ); + } + + public NClob fromString(String string) { + return NClobProxy.generateProxy( string ); + } + + @Override + @SuppressWarnings({ "unchecked" }) + public Comparator getComparator() { + return IncomparableComparator.INSTANCE; + } + + @Override + public int extractHashCode(NClob value) { + return System.identityHashCode( value ); + } + + @Override + public boolean areEqual(NClob one, NClob another) { + return one == another; + } + + @SuppressWarnings({ "unchecked" }) + public X unwrap(final NClob value, Class type, WrapperOptions options) { + if ( value == null ) { + return null; + } + + try { + if ( CharacterStream.class.isAssignableFrom( type ) ) { + if ( NClobImplementer.class.isInstance( value ) ) { + // if the incoming NClob is a wrapper, just pass along its BinaryStream + return (X) ( (NClobImplementer) value ).getUnderlyingStream(); + } + else { + // otherwise we need to build a BinaryStream... + return (X) new CharacterStreamImpl( DataHelper.extractString( value.getCharacterStream() ) ); + } + } + else if (NClob.class.isAssignableFrom( type )) { + final NClob nclob = WrappedNClob.class.isInstance( value ) + ? ( (WrappedNClob) value ).getWrappedNClob() + : value; + return (X) nclob; + } + } + catch ( SQLException e ) { + throw new HibernateException( "Unable to access nclob stream", e ); + } + + throw unknownUnwrap( type ); + } + + public NClob wrap(X value, WrapperOptions options) { + if ( value == null ) { + return null; + } + + // Support multiple return types from + // org.hibernate.type.descriptor.sql.ClobTypeDescriptor + if ( NClob.class.isAssignableFrom( value.getClass() ) ) { + return options.getLobCreator().wrap( (NClob) value ); + } + else if ( Reader.class.isAssignableFrom( value.getClass() ) ) { + Reader reader = (Reader) value; + return options.getLobCreator().createNClob( DataHelper.extractString( reader ) ); + } + + throw unknownWrap( value.getClass() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java index 23b38ad56f..8af63b85c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java @@ -31,6 +31,7 @@ import java.sql.SQLException; import java.sql.Types; import org.hibernate.engine.jdbc.BinaryStream; +import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -56,11 +57,24 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { return true; } - protected abstract BasicExtractor getBlobExtractor(final JavaTypeDescriptor javaTypeDescriptor); - @Override - public BasicExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { - return getBlobExtractor( javaTypeDescriptor ); + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getBlob( name ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( name ), options ); + } + }; } protected abstract BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor); @@ -81,41 +95,17 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { BlobTypeDescriptor descriptor = BLOB_BINDING; - if ( options.useStreamForLobBinding() ) { - descriptor = STREAM_BINDING; - } - else if ( byte[].class.isInstance( value ) ) { + if ( byte[].class.isInstance( value ) ) { // performance shortcut for binding BLOB data in byte[] format descriptor = PRIMITIVE_ARRAY_BINDING; } + else if ( options.useStreamForLobBinding() ) { + descriptor = STREAM_BINDING; + } descriptor.getBlobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); } }; } - - @Override - public BasicExtractor getBlobExtractor(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicExtractor( javaTypeDescriptor, this ) { - // For now, default to using getBlob. If extraction - // should also check useStreamForLobBinding, add - // checks here and use STREAM_BINDING. - - @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return BLOB_BINDING.getExtractor( javaTypeDescriptor ).doExtract( rs, name, options ); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - return BLOB_BINDING.getExtractor( javaTypeDescriptor ).doExtract( statement, index, options ); - } - - @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - return BLOB_BINDING.getExtractor( javaTypeDescriptor ).doExtract( statement, name, options ); - } - }; - } }; public static final BlobTypeDescriptor PRIMITIVE_ARRAY_BINDING = @@ -130,26 +120,6 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { } }; } - - @Override - public BasicExtractor getBlobExtractor(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicExtractor( javaTypeDescriptor, this ) { - @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( rs.getBytes( name ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( statement.getBytes( index ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( statement.getBytes( name ), options ); - } - }; - } }; public static final BlobTypeDescriptor BLOB_BINDING = @@ -164,26 +134,6 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { } }; } - - @Override - public BasicExtractor getBlobExtractor(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicExtractor( javaTypeDescriptor, this ) { - @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( rs.getBlob( name ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( statement.getBlob( index ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( statement.getBlob( name ), options ); - } - }; - } }; public static final BlobTypeDescriptor STREAM_BINDING = @@ -199,28 +149,6 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { } }; } - - @Override - public BasicExtractor getBlobExtractor(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicExtractor( javaTypeDescriptor, this ) { - @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( rs.getBinaryStream( name ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - // TODO: CallableStatement does not have getBinaryStream - return javaTypeDescriptor.wrap( statement.getBytes( index ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - // TODO: CallableStatement does not have getBinaryStream - return javaTypeDescriptor.wrap( statement.getBytes( name ), options ); - } - }; - } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java index 816b6acc60..4cbc2c3c90 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java @@ -57,36 +57,40 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( rs.getClob( name ), options ); - } + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getClob( name ), options ); + } @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { return javaTypeDescriptor.wrap( statement.getClob( index ), options ); } @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { return javaTypeDescriptor.wrap( statement.getClob( name ), options ); } }; } + protected abstract BasicBinder getClobBinder(JavaTypeDescriptor javaTypeDescriptor); - protected abstract BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor); - - public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + @Override + public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { return getClobBinder( javaTypeDescriptor ); } + public static final ClobTypeDescriptor DEFAULT = new ClobTypeDescriptor() { { SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); } - public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + @Override + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { @@ -103,7 +107,8 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { public static final ClobTypeDescriptor CLOB_BINDING = new ClobTypeDescriptor() { - public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + @Override + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) @@ -116,7 +121,8 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { public static final ClobTypeDescriptor STREAM_BINDING = new ClobTypeDescriptor() { - public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + @Override + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) @@ -128,4 +134,41 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { } }; + public static final ClobTypeDescriptor STREAM_BINDING_EXTRACTING = + new ClobTypeDescriptor() { + @Override + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); + st.setCharacterStream( index, characterStream.asReader(), characterStream.getLength() ); + } + }; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getCharacterStream( name ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { + return javaTypeDescriptor.wrap( statement.getCharacterStream( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + return javaTypeDescriptor.wrap( statement.getCharacterStream( name ), options ); + } + }; + } + }; + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java index 4364709e9b..bc768a0154 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java @@ -111,7 +111,7 @@ public class JdbcTypeJavaClassMappings { public int determineJdbcTypeCodeForJavaClass(Class cls) { Integer typeCode = javaClassToJdbcTypeCodeMap.get( cls ); if ( typeCode != null ) { - return typeCode.intValue(); + return typeCode; } int specialCode = cls.hashCode(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java index 588e0fc80b..bbf15abc27 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java @@ -57,53 +57,58 @@ public abstract class NClobTypeDescriptor implements SqlTypeDescriptor { public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( rs.getNClob( name ), options ); - } + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getNClob( name ), options ); + } @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { return javaTypeDescriptor.wrap( statement.getNClob( index ), options ); } @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { return javaTypeDescriptor.wrap( statement.getNClob( name ), options ); } }; } + protected abstract BasicBinder getNClobBinder(JavaTypeDescriptor javaTypeDescriptor); - protected abstract BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor); - - public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { - return getClobBinder( javaTypeDescriptor ); + @Override + public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { + return getNClobBinder( javaTypeDescriptor ); } - public static final ClobTypeDescriptor DEFAULT = - new ClobTypeDescriptor() { + + public static final NClobTypeDescriptor DEFAULT = + new NClobTypeDescriptor() { { SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); } - public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + @Override + public BasicBinder getNClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { if ( options.useStreamForLobBinding() ) { - STREAM_BINDING.getClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + STREAM_BINDING.getNClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); } else { - CLOB_BINDING.getClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + NCLOB_BINDING.getNClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); } } }; } }; - public static final ClobTypeDescriptor CLOB_BINDING = - new ClobTypeDescriptor() { - public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + public static final NClobTypeDescriptor NCLOB_BINDING = + new NClobTypeDescriptor() { + @Override + public BasicBinder getNClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) @@ -114,9 +119,10 @@ public abstract class NClobTypeDescriptor implements SqlTypeDescriptor { } }; - public static final ClobTypeDescriptor STREAM_BINDING = - new ClobTypeDescriptor() { - public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + public static final NClobTypeDescriptor STREAM_BINDING = + new NClobTypeDescriptor() { + @Override + public BasicBinder getNClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) diff --git a/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd b/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd index 5a3706a537..01ff3d390c 100644 --- a/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd +++ b/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd @@ -993,7 +993,7 @@ finder methods for named queries --> - + @@ -1008,12 +1008,12 @@ finder methods for named queries --> - + - + diff --git a/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd b/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd index 000bec0336..737ed29a98 100644 --- a/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd +++ b/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd @@ -1700,6 +1700,7 @@ arbitrary number of queries, and import declarations of arbitrary classes. + diff --git a/hibernate-core/src/test/java/org/hibernate/connection/DriverManagerRegistrationTest.java b/hibernate-core/src/test/java/org/hibernate/connection/DriverManagerRegistrationTest.java index bb91d5c66f..9bb4798a72 100644 --- a/hibernate-core/src/test/java/org/hibernate/connection/DriverManagerRegistrationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/connection/DriverManagerRegistrationTest.java @@ -35,6 +35,7 @@ import java.util.logging.Logger; import org.junit.AfterClass; import org.junit.Test; +import org.hibernate.internal.util.ClassLoaderHelper; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; @@ -91,7 +92,7 @@ public class DriverManagerRegistrationTest extends BaseUnitTestCase { } private static ClassLoader determineClassLoader() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ClassLoader cl = ClassLoaderHelper.getContextClassLoader(); if ( cl == null ) { cl = DriverManagerRegistrationTest.class.getClassLoader(); } diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java index 6ccda4ea77..da2924d3ea 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java @@ -153,6 +153,47 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase { ); } + @Test + @TestForIssue(jiraKey = "HHH-7781") + public void testGetLimitStringWithCastOperator() { + final String query = "select cast(lc302_doku6_.redniBrojStavke as varchar(255)) as col_0_0_, lc302_doku6_.dokumentiID as col_1_0_ " + + "from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC"; + + assertEquals( + "WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " + + "select TOP(?) cast(lc302_doku6_.redniBrojStavke as varchar(255)) as col_0_0_, lc302_doku6_.dokumentiID as col_1_0_ " + + "from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC ) inner_query ) " + + "SELECT col_0_0_, col_1_0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.buildLimitHandler( query, toRowSelection( 1, 3 ) ).getProcessedSql() + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-8007") + public void testGetLimitStringSelectingMultipleColumnsFromSeveralTables() { + final String query = "select t1.*, t2.* from tab1 t1, tab2 t2 where t1.ref = t2.ref order by t1.id desc"; + + assertEquals( + "WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " + + "select TOP(?) t1.*, t2.* from tab1 t1, tab2 t2 where t1.ref = t2.ref order by t1.id desc ) inner_query ) " + + "SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.buildLimitHandler( query, toRowSelection( 1, 3 ) ).getProcessedSql() + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-8007") + public void testGetLimitStringSelectingAllColumns() { + final String query = "select * from tab1 t1, tab2 t2 where t1.ref = t2.ref order by t1.id desc"; + + assertEquals( + "WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " + + "select TOP(?) * from tab1 t1, tab2 t2 where t1.ref = t2.ref order by t1.id desc ) inner_query ) " + + "SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.buildLimitHandler( query, toRowSelection( 1, 3 ) ).getProcessedSql() + ); + } + private RowSelection toRowSelection(int firstRow, int maxRows) { RowSelection selection = new RowSelection(); selection.setFirstRow( firstRow ); diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/TestingDialects.java b/hibernate-core/src/test/java/org/hibernate/dialect/TestingDialects.java index 8c7bb01edf..4f1db81f3c 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/TestingDialects.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/TestingDialects.java @@ -26,8 +26,8 @@ import java.sql.DatabaseMetaData; import java.sql.SQLException; import org.hibernate.HibernateException; -import org.hibernate.engine.jdbc.dialect.internal.AbstractDialectResolver; import org.hibernate.engine.jdbc.dialect.internal.BasicDialectResolver; +import org.hibernate.engine.jdbc.dialect.spi.AbstractDatabaseMetaDataDialectResolver; /** * @author Steve Ebersole @@ -46,7 +46,7 @@ public class TestingDialects { public static class MySpecialDB2Dialect extends Dialect { } - public static class MyDialectResolver1 extends AbstractDialectResolver { + public static class MyDialectResolver1 extends AbstractDatabaseMetaDataDialectResolver { protected Dialect resolveDialectInternal(DatabaseMetaData metaData) throws SQLException { String databaseName = metaData.getDatabaseProductName(); int databaseMajorVersion = metaData.getDatabaseMajorVersion(); @@ -71,7 +71,7 @@ public class TestingDialects { } } - public static class ErrorDialectResolver1 extends AbstractDialectResolver { + public static class ErrorDialectResolver1 extends AbstractDatabaseMetaDataDialectResolver { public Dialect resolveDialectInternal(DatabaseMetaData metaData) throws SQLException { String databaseName = metaData.getDatabaseProductName(); if ( databaseName.equals( "ConnectionErrorDatabase1" ) ) { @@ -83,7 +83,7 @@ public class TestingDialects { } } - public static class ErrorDialectResolver2 extends AbstractDialectResolver { + public static class ErrorDialectResolver2 extends AbstractDatabaseMetaDataDialectResolver { public Dialect resolveDialectInternal(DatabaseMetaData metaData) throws SQLException { String databaseName = metaData.getDatabaseProductName(); if ( databaseName.equals( "ErrorDatabase1" ) ) { diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java index 977c219a44..b935e5b07c 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java @@ -60,7 +60,8 @@ import org.hibernate.dialect.SybaseAnywhereDialect; import org.hibernate.dialect.TestingDialects; import org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl; import org.hibernate.engine.jdbc.dialect.internal.DialectResolverSet; -import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver; +import org.hibernate.engine.jdbc.dialect.internal.StandardDatabaseInfoDialectResolver; +import org.hibernate.engine.jdbc.dialect.internal.StandardDatabaseMetaDataDialectResolver; import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; import org.hibernate.testing.junit4.BaseUnitTestCase; @@ -80,7 +81,9 @@ public class DialectFactoryTest extends BaseUnitTestCase { dialectFactory.setStrategySelector( new StrategySelectorBuilder().buildSelector( new ClassLoaderServiceImpl( getClass().getClassLoader() ) ) ); - dialectFactory.setDialectResolver( new StandardDialectResolver() ); + dialectFactory.setDialectResolver( + new StandardDatabaseMetaDataDialectResolver( StandardDatabaseInfoDialectResolver.INSTANCE ) + ); } @Test @@ -138,7 +141,7 @@ public class DialectFactoryTest extends BaseUnitTestCase { @Test public void testPreregisteredDialects() { - DialectResolver resolver = new StandardDialectResolver(); + DialectResolver resolver = new StandardDatabaseMetaDataDialectResolver( StandardDatabaseInfoDialectResolver.INSTANCE ); testDetermination( "HSQL Database Engine", HSQLDialect.class, resolver ); testDetermination( "H2", H2Dialect.class, resolver ); testDetermination( "MySQL", MySQLDialect.class, resolver ); diff --git a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDatabaseMetaDataDialectResolverTest.java similarity index 94% rename from hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java rename to hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDatabaseMetaDataDialectResolverTest.java index 4bc0abb606..12f711da00 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDatabaseMetaDataDialectResolverTest.java @@ -42,11 +42,11 @@ import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; /** - * Unit test of the {@link StandardDialectResolver} class. + * Unit test of the {@link StandardDatabaseMetaDataDialectResolver} class. * * @author Bryan Turner */ -public class StandardDialectResolverTest extends BaseUnitTestCase { +public class StandardDatabaseMetaDataDialectResolverTest extends BaseUnitTestCase { @Test public void testResolveDialectInternalForSQLServer2000() @@ -137,8 +137,8 @@ public class StandardDialectResolverTest extends BaseUnitTestCase { when( metaData.getDatabaseMajorVersion() ).thenReturn( majorVersion ); when( metaData.getDatabaseMinorVersion() ).thenReturn( minorVersion ); - Dialect dialect = new StandardDialectResolver().resolveDialectInternal( - metaData ); + Dialect dialect = new StandardDatabaseMetaDataDialectResolver( StandardDatabaseInfoDialectResolver.INSTANCE ) + .resolveDialectInternal( metaData ); StringBuilder builder = new StringBuilder( productName ).append( " " ) .append( majorVersion ); diff --git a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorNoIncrementTest.java b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorNoIncrementTest.java index 6d153a24d5..fe81d34d56 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorNoIncrementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorNoIncrementTest.java @@ -23,16 +23,14 @@ */ package org.hibernate.id; +import static org.junit.Assert.assertEquals; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import org.hibernate.Session; import org.hibernate.TestingDatabaseInfo; import org.hibernate.cfg.Configuration; @@ -50,8 +48,9 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.ServiceRegistryBuilder; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.type.StandardBasicTypes; - -import static org.junit.Assert.assertEquals; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; /** * I went back to 3.3 source and grabbed the code/logic as it existed back then and crafted this @@ -127,7 +126,7 @@ public class SequenceHiLoGeneratorNoIncrementTest extends BaseUnitTestCase { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // initially sequence should be uninitialized - assertEquals( 0L, extractSequenceValue( ((Session)session) ) ); + assertEquals( 0L, extractSequenceValue( (session) ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // historically the hilo generators skipped the initial block of values; @@ -135,44 +134,45 @@ public class SequenceHiLoGeneratorNoIncrementTest extends BaseUnitTestCase { Long generatedValue = (Long) generator.generate( session, null ); assertEquals( 1L, generatedValue.longValue() ); // which should also perform the first read on the sequence which should set it to its "start with" value (1) - assertEquals( 1L, extractSequenceValue( ((Session)session) ) ); + assertEquals( 1L, extractSequenceValue( (session) ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ generatedValue = (Long) generator.generate( session, null ); assertEquals( 2L, generatedValue.longValue() ); - assertEquals( 2L, extractSequenceValue( ((Session)session) ) ); + assertEquals( 2L, extractSequenceValue( (session) ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ generatedValue = (Long) generator.generate( session, null ); assertEquals( 3L, generatedValue.longValue() ); - assertEquals( 3L, extractSequenceValue( ((Session)session) ) ); + assertEquals( 3L, extractSequenceValue( (session) ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ generatedValue = (Long) generator.generate( session, null ); assertEquals( 4L, generatedValue.longValue() ); - assertEquals( 4L, extractSequenceValue( ((Session)session) ) ); + assertEquals( 4L, extractSequenceValue( (session) ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ generatedValue = (Long) generator.generate( session, null ); assertEquals( 5L, generatedValue.longValue() ); - assertEquals( 5L, extractSequenceValue( ((Session)session) ) ); + assertEquals( 5L, extractSequenceValue( (session) ) ); ((Session)session).getTransaction().commit(); ((Session)session).close(); } - private long extractSequenceValue(Session session) { + private long extractSequenceValue(final SessionImplementor session) { class WorkImpl implements Work { private long value; public void execute(Connection connection) throws SQLException { - PreparedStatement query = connection.prepareStatement( "select currval('" + TEST_SEQUENCE + "');" ); - ResultSet resultSet = query.executeQuery(); + + PreparedStatement query = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( "select currval('" + TEST_SEQUENCE + "');" ); + ResultSet resultSet = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( query ); resultSet.next(); value = resultSet.getLong( 1 ); } } WorkImpl work = new WorkImpl(); - session.doWork( work ); + ( (Session) session ).doWork( work ); return work.value; } } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java index 7360d8c297..6fe9ff8064 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/SequenceHiLoGeneratorTest.java @@ -23,16 +23,14 @@ */ package org.hibernate.id; +import static org.junit.Assert.assertEquals; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import org.hibernate.Session; import org.hibernate.TestingDatabaseInfo; import org.hibernate.cfg.Configuration; @@ -42,6 +40,7 @@ import org.hibernate.cfg.ObjectNameNormalizer; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.SessionImpl; import org.hibernate.jdbc.Work; import org.hibernate.mapping.SimpleAuxiliaryDatabaseObject; @@ -49,13 +48,14 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.ServiceRegistryBuilder; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.type.StandardBasicTypes; - -import static org.junit.Assert.assertEquals; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; /** * I went back to 3.3 source and grabbed the code/logic as it existed back then and crafted this * unit test so that we can make sure the value keep being generated in the expected manner - * + * * @author Steve Ebersole */ @SuppressWarnings({ "deprecation" }) @@ -72,34 +72,26 @@ public class SequenceHiLoGeneratorTest extends BaseUnitTestCase { Properties properties = new Properties(); properties.setProperty( SequenceGenerator.SEQUENCE, TEST_SEQUENCE ); properties.setProperty( SequenceHiLoGenerator.MAX_LO, "3" ); - properties.put( - PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER, - new ObjectNameNormalizer() { - @Override - protected boolean isUseQuotedIdentifiersGlobally() { - return false; - } + properties.put( PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER, new ObjectNameNormalizer() { + @Override + protected boolean isUseQuotedIdentifiersGlobally() { + return false; + } - @Override - protected NamingStrategy getNamingStrategy() { - return cfg.getNamingStrategy(); - } - } - ); + @Override + protected NamingStrategy getNamingStrategy() { + return cfg.getNamingStrategy(); + } + } ); Dialect dialect = new H2Dialect(); generator = new SequenceHiLoGenerator(); generator.configure( StandardBasicTypes.LONG, properties, dialect ); - cfg = TestingDatabaseInfo.buildBaseConfiguration() - .setProperty( Environment.HBM2DDL_AUTO, "create-drop" ); - cfg.addAuxiliaryDatabaseObject( - new SimpleAuxiliaryDatabaseObject( - generator.sqlCreateStrings( dialect )[0], - generator.sqlDropStrings( dialect )[0] - ) - ); + cfg = TestingDatabaseInfo.buildBaseConfiguration().setProperty( Environment.HBM2DDL_AUTO, "create-drop" ); + cfg.addAuxiliaryDatabaseObject( new SimpleAuxiliaryDatabaseObject( generator.sqlCreateStrings( dialect )[0], + generator.sqlDropStrings( dialect )[0] ) ); serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( cfg.getProperties() ); sessionFactory = (SessionFactoryImplementor) cfg.buildSessionFactory( serviceRegistry ); } @@ -125,7 +117,7 @@ public class SequenceHiLoGeneratorTest extends BaseUnitTestCase { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // historically the hilo generators skipped the initial block of values; - // so the first generated id value is maxlo + 1, here be 4 + // so the first generated id value is maxlo + 1, here be 4 Long generatedValue = (Long) generator.generate( session, null ); assertEquals( 4L, generatedValue.longValue() ); // which should also perform the first read on the sequence which should set it to its "start with" value (1) @@ -144,8 +136,8 @@ public class SequenceHiLoGeneratorTest extends BaseUnitTestCase { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ generatedValue = (Long) generator.generate( session, null ); assertEquals( 7L, generatedValue.longValue() ); - // unlike the newer strategies, the db value will not get update here. It gets updated on the next invocation - // after a clock over + // unlike the newer strategies, the db value will not get update here. It gets updated on the next invocation + // after a clock over assertEquals( 1L, extractSequenceValue( session ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -158,18 +150,19 @@ public class SequenceHiLoGeneratorTest extends BaseUnitTestCase { session.close(); } - private long extractSequenceValue(Session session) { + private long extractSequenceValue(final SessionImplementor session) { class WorkImpl implements Work { private long value; + public void execute(Connection connection) throws SQLException { - PreparedStatement query = connection.prepareStatement( "select currval('" + TEST_SEQUENCE + "');" ); - ResultSet resultSet = query.executeQuery(); + PreparedStatement query = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( "select currval('" + TEST_SEQUENCE + "');" ); + ResultSet resultSet = session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( query ); resultSet.next(); value = resultSet.getLong( 1 ); } } WorkImpl work = new WorkImpl(); - session.doWork( work ); + ( (Session) session ).doWork( work ); return work.value; } } diff --git a/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java b/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java index 7b8ee0133a..360a2d9421 100644 --- a/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java +++ b/hibernate-core/src/test/java/org/hibernate/metamodel/internal/source/annotations/xml/OrmXmlParserTests.java @@ -23,7 +23,7 @@ */ package org.hibernate.metamodel.internal.source.annotations.xml; -import org.junit.Test; +import static junit.framework.Assert.assertNotNull; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.metamodel.MetadataSources; @@ -31,8 +31,7 @@ import org.hibernate.metamodel.internal.MetadataImpl; import org.hibernate.metamodel.spi.binding.EntityBinding; import org.hibernate.metamodel.spi.source.MappingException; import org.hibernate.testing.junit4.BaseUnitTestCase; - -import static junit.framework.Assert.assertNotNull; +import org.junit.Test; /** * @author Hardy Ferentschik @@ -53,7 +52,6 @@ public class OrmXmlParserTests extends BaseUnitTestCase { MetadataSources sources = new MetadataSources( new StandardServiceRegistryBuilder().build() ); sources.addResource( "org/hibernate/metamodel/internal/source/annotations/xml/orm-star.xml" ); MetadataImpl metadata = (MetadataImpl) sources.buildMetadata(); - EntityBinding binding = metadata.getEntityBinding( Star.class.getName() ); assertNotNull( binding ); } diff --git a/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java b/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java index 94436ee48d..74cd5489e2 100644 --- a/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java @@ -27,6 +27,12 @@ import java.lang.reflect.Field; import java.util.List; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + import org.hibernate.IrrelevantEntity; import org.hibernate.Session; @@ -42,12 +48,6 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - /** * @author Steve Ebersole */ @@ -67,8 +67,6 @@ public class SessionWithSharedConnectionTest extends BaseCoreFunctionalTestCase assertFalse( ((SessionImplementor) secondSession).getTransactionCoordinator() .getJdbcCoordinator() - .getLogicalConnection() - .getResourceRegistry() .hasRegisteredResources() ); diff --git a/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java b/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java index 38d3adae87..58e99494b1 100644 --- a/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/sql/TemplateTest.java @@ -25,16 +25,23 @@ package org.hibernate.sql; import java.util.Collections; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import org.hibernate.QueryException; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.function.SQLFunctionRegistry; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.entity.PropertyMapping; +import org.hibernate.service.ServiceRegistry; import org.hibernate.sql.ordering.antlr.ColumnMapper; import org.hibernate.sql.ordering.antlr.ColumnReference; import org.hibernate.sql.ordering.antlr.SqlValueReference; +import org.hibernate.testing.ServiceRegistryBuilder; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.type.Type; @@ -99,6 +106,23 @@ public class TemplateTest extends BaseUnitTestCase { private static final SQLFunctionRegistry FUNCTION_REGISTRY = new SQLFunctionRegistry( DIALECT, Collections.EMPTY_MAP ); + private static SessionFactoryImplementor SESSION_FACTORY = null; // Required for ORDER BY rendering. + + @BeforeClass + public static void buildSessionFactory() { + Configuration cfg = new Configuration(); + cfg.setProperty( AvailableSettings.DIALECT, DIALECT.getClass().getName() ); + ServiceRegistry serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( cfg.getProperties() ); + SESSION_FACTORY = (SessionFactoryImplementor) cfg.buildSessionFactory( serviceRegistry ); + } + + @AfterClass + public static void closeSessionFactory() { + if ( SESSION_FACTORY != null ) { + SESSION_FACTORY.close(); + } + } + @Test public void testSqlExtractFunction() { String fragment = "extract( year from col )"; @@ -243,6 +267,6 @@ public class TemplateTest extends BaseUnitTestCase { } public String doStandardRendering(String fragment) { - return Template.renderOrderByStringTemplate( fragment, MAPPER, null, DIALECT, FUNCTION_REGISTRY ); + return Template.renderOrderByStringTemplate( fragment, MAPPER, SESSION_FACTORY, DIALECT, FUNCTION_REGISTRY ); } } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/dataTypes/BasicOperationsTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/dataTypes/BasicOperationsTest.java index fbb50270da..ef990efbe3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/dataTypes/BasicOperationsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/dataTypes/BasicOperationsTest.java @@ -35,6 +35,7 @@ import org.junit.Test; import org.hibernate.Session; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.testing.DialectCheck; import org.hibernate.testing.DialectChecks; @@ -71,9 +72,9 @@ public class BasicOperationsTest extends BaseCoreFunctionalTestCase { Session s = openSession(); - s.doWork( new ValidateSomeEntityColumns() ); - s.doWork( new ValidateRowCount( SOME_ENTITY_TABLE_NAME, 0 ) ); - s.doWork( new ValidateRowCount( SOME_OTHER_ENTITY_TABLE_NAME, 0 ) ); + s.doWork( new ValidateSomeEntityColumns( (SessionImplementor) s ) ); + s.doWork( new ValidateRowCount( (SessionImplementor) s, SOME_ENTITY_TABLE_NAME, 0 ) ); + s.doWork( new ValidateRowCount( (SessionImplementor) s, SOME_OTHER_ENTITY_TABLE_NAME, 0 ) ); s.beginTransaction(); SomeEntity someEntity = new SomeEntity( now ); @@ -85,22 +86,28 @@ public class BasicOperationsTest extends BaseCoreFunctionalTestCase { s = openSession(); - s.doWork( new ValidateRowCount( SOME_ENTITY_TABLE_NAME, 1 ) ); - s.doWork( new ValidateRowCount( SOME_OTHER_ENTITY_TABLE_NAME, 1 ) ); + s.doWork( new ValidateRowCount( (SessionImplementor) s, SOME_ENTITY_TABLE_NAME, 1 ) ); + s.doWork( new ValidateRowCount( (SessionImplementor) s, SOME_OTHER_ENTITY_TABLE_NAME, 1 ) ); s.beginTransaction(); s.delete( someEntity ); s.delete( someOtherEntity ); s.getTransaction().commit(); - s.doWork( new ValidateRowCount( SOME_ENTITY_TABLE_NAME, 0 ) ); - s.doWork( new ValidateRowCount( SOME_OTHER_ENTITY_TABLE_NAME, 0 ) ); + s.doWork( new ValidateRowCount( (SessionImplementor) s, SOME_ENTITY_TABLE_NAME, 0 ) ); + s.doWork( new ValidateRowCount( (SessionImplementor) s, SOME_OTHER_ENTITY_TABLE_NAME, 0 ) ); s.close(); } // verify all the expected columns are created class ValidateSomeEntityColumns implements Work { + private SessionImplementor s; + + public ValidateSomeEntityColumns( SessionImplementor s ) { + this.s = s; + } + public void execute(Connection connection) throws SQLException { // id -> java.util.Date (DATE - becase of explicit TemporalType) validateColumn( connection, "ID", java.sql.Types.DATE ); @@ -122,9 +129,10 @@ public class BasicOperationsTest extends BaseCoreFunctionalTestCase { String columnNamePattern = generateFinalNamePattern( meta, columnName ); ResultSet columnInfo = meta.getColumns( null, null, tableNamePattern, columnNamePattern ); + s.getTransactionCoordinator().getJdbcCoordinator().register(columnInfo); assertTrue( columnInfo.next() ); int dataType = columnInfo.getInt( "DATA_TYPE" ); - columnInfo.close(); + s.getTransactionCoordinator().getJdbcCoordinator().release( columnInfo ); assertEquals( columnName, JdbcTypeNameMapper.getTypeName( expectedJdbcTypeCode ), @@ -147,14 +155,18 @@ public class BasicOperationsTest extends BaseCoreFunctionalTestCase { private final int expectedRowCount; private final String table; - public ValidateRowCount(String table, int count) { + private SessionImplementor s; + + public ValidateRowCount(SessionImplementor s, String table, int count) { + this.s = s; this.expectedRowCount = count; this.table = table; } public void execute(Connection connection) throws SQLException { - Statement st = connection.createStatement(); - ResultSet result = st.executeQuery( "SELECT COUNT(*) FROM " + table ); + Statement st = s.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); + s.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st, "SELECT COUNT(*) FROM " + table ); + ResultSet result = s.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st, "SELECT COUNT(*) FROM " + table ); result.next(); int rowCount = result.getInt( 1 ); assertEquals( "Unexpected row count", expectedRowCount, rowCount ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/Book.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/Book.java index 7e321e341c..42699f0d81 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/Book.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/Book.java @@ -6,13 +6,14 @@ import javax.persistence.AttributeOverrides; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.Index; import javax.persistence.SecondaryTable; /** * @author Emmanuel Bernard */ @Entity -@SecondaryTable(name = "BookSummary") +@SecondaryTable(name = "BookSummary", indexes = @Index( columnList = "summ_size ASC, text DESC")) public class Book { private String isbn; private String name; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddableA.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddableA.java new file mode 100644 index 0000000000..36b7a66048 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddableA.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.annotations.embedded; + +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; + +/** + * @author Brett Meyer + */ +@Embeddable +public class EmbeddableA { + + @Embedded + @AttributeOverrides({@AttributeOverride(name = "embedAttrB" , column = @Column(table = "TableB"))}) + private EmbeddableB embedB; + + private String embedAttrA; + + public EmbeddableB getEmbedB() { + return embedB; + } + + public void setEmbedB(EmbeddableB embedB) { + this.embedB = embedB; + } + + public String getEmbedAttrA() { + return embedAttrA; + } + + public void setEmbedAttrA(String embedAttrA) { + this.embedAttrA = embedAttrA; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddableB.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddableB.java new file mode 100644 index 0000000000..85f0e53728 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddableB.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.annotations.embedded; + +import javax.persistence.Embeddable; + +/** + * @author Brett Meyer + */ +@Embeddable +public class EmbeddableB { + + private String embedAttrB; + + public String getEmbedAttrB() { + return embedAttrB; + } + + public void setEmbedAttrB(String embedAttrB) { + this.embedAttrB = embedAttrB; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EntityWithNestedEmbeddables.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EntityWithNestedEmbeddables.java new file mode 100644 index 0000000000..e310a95d2f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EntityWithNestedEmbeddables.java @@ -0,0 +1,60 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.annotations.embedded; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.SecondaryTable; +import javax.persistence.SecondaryTables; +import javax.persistence.Table; + +/** + * @author Brett Meyer + */ +@Entity +@Table(name="TableA") +@SecondaryTables({@SecondaryTable(name = "TableB")}) +public class EntityWithNestedEmbeddables { + @Id + @GeneratedValue + private Integer id; + + @Embedded + private EmbeddableA embedA; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public EmbeddableA getEmbedA() { + return embedA; + } + + public void setEmbedA(EmbeddableA embedA) { + this.embedA = embedA; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/NestedEmbeddableAttributeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/NestedEmbeddableAttributeOverrideTest.java new file mode 100644 index 0000000000..569ae8ab3e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/NestedEmbeddableAttributeOverrideTest.java @@ -0,0 +1,59 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.annotations.embedded; + +import org.hibernate.Session; +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * @author Brett Meyer + */ +@FailureExpected(jiraKey="HHH-8021") +public class NestedEmbeddableAttributeOverrideTest extends BaseCoreFunctionalTestCase { + + @Test + @TestForIssue(jiraKey="HHH-8021") + public void testAttributeOverride() { + EmbeddableB embedB = new EmbeddableB(); + embedB.setEmbedAttrB( "B" ); + + EmbeddableA embedA = new EmbeddableA(); + embedA.setEmbedAttrA("A"); + embedA.setEmbedB(embedB); + + EntityWithNestedEmbeddables entity = new EntityWithNestedEmbeddables(); + entity.setEmbedA(embedA); + + Session s = openSession(); + s.beginTransaction(); + s.persist( entity ); + s.getTransaction().commit(); + s.close(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EntityWithNestedEmbeddables.class }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java index d859fe3911..ae3ab486e6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java @@ -4,6 +4,7 @@ import java.util.Set; import javax.persistence.CollectionTable; import javax.persistence.ElementCollection; import javax.persistence.Entity; +import javax.persistence.Index; @Entity public class WealthyPerson extends Person { @@ -15,6 +16,6 @@ public class WealthyPerson extends Person { protected Set

legacyVacationHomes = new HashSet
(); @ElementCollection - @CollectionTable(name = "WelPers_VacHomes") + @CollectionTable(name = "WelPers_VacHomes", indexes = @Index( columnList = "countryName, type_id")) protected Set
explicitVacationHomes = new HashSet
(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/idmanytoone/ShoppingBasketsPK.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/idmanytoone/ShoppingBasketsPK.java index 0007d5ad33..27f8a2a8e3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/idmanytoone/ShoppingBasketsPK.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/idmanytoone/ShoppingBasketsPK.java @@ -33,7 +33,7 @@ public class ShoppingBasketsPK implements Serializable { public int hashCode() { int hashcode = 0; if (getOwner() != null) { - hashcode = hashcode + (int) getOwner().getORMID(); + hashcode = hashcode + getOwner().getORMID(); } hashcode = hashcode + (getBasketDatetime() == null ? 0 : getBasketDatetime().hashCode()); return hashcode; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Car.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Car.java new file mode 100644 index 0000000000..a0bea61957 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Car.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.index.jpa; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.Table; + + +/** + * @author Strong Liu + */ +@Entity +@Table( indexes = {@Index( unique = true, columnList = "brand, producer") +, @Index( name = "Car_idx", columnList = "since DESC")}) +public class Car { + @Id + long id; + String brand; + String producer; + long since; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Dealer.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Dealer.java new file mode 100644 index 0000000000..3c01d5f5f1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/Dealer.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.index.jpa; + +import javax.persistence.Entity; + +/** + * @author Strong Liu + */ +@Entity +public class Dealer { +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/IndexTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/IndexTest.java new file mode 100644 index 0000000000..a9d90316ac --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/index/jpa/IndexTest.java @@ -0,0 +1,163 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.index.jpa; + +import java.util.Iterator; + +import org.junit.Test; + +import static org.junit.Assert.*; + +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.Bag; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Index; +import org.hibernate.mapping.Join; +import org.hibernate.mapping.List; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Set; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.UniqueKey; +import org.hibernate.mapping.Value; +import org.hibernate.test.annotations.embedded.Address; +import org.hibernate.test.annotations.embedded.AddressType; +import org.hibernate.test.annotations.embedded.Book; +import org.hibernate.test.annotations.embedded.Person; +import org.hibernate.test.annotations.embedded.Summary; +import org.hibernate.test.annotations.embedded.WealthyPerson; +import org.hibernate.test.event.collection.detached.*; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +/** + * @author Strong Liu + */ +public class IndexTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Car.class, + Book.class, + Summary.class, + WealthyPerson.class, + Person.class, + AddressType.class, + Address.class, + Alias.class, + org.hibernate.test.event.collection.detached.Character.class + }; + } + + @Test + public void testTableIndex() { + PersistentClass entity = configuration().getClassMapping( Car.class.getName() ); + Iterator itr = entity.getTable().getUniqueKeyIterator(); + assertTrue( itr.hasNext() ); + UniqueKey uk = (UniqueKey) itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( StringHelper.isNotEmpty( uk.getName() ) ); + assertEquals( 2, uk.getColumnSpan() ); + Column column = (Column) uk.getColumns().get( 0 ); + assertEquals( "brand", column.getName() ); + column = (Column) uk.getColumns().get( 1 ); + assertEquals( "producer", column.getName() ); + assertSame( entity.getTable(), uk.getTable() ); + + + itr = entity.getTable().getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = (Index)itr.next(); + assertFalse( itr.hasNext() ); + assertEquals( "Car_idx", index.getName() ); + assertEquals( 1, index.getColumnSpan() ); + column = index.getColumnIterator().next(); + assertEquals( "since", column.getName() ); + assertSame( entity.getTable(), index.getTable() ); + } + + @Test + public void testSecondaryTableIndex(){ + PersistentClass entity = configuration().getClassMapping( Book.class.getName() ); + + Join join = (Join)entity.getJoinIterator().next(); + Iterator itr = join.getTable().getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); + assertEquals( 2, index.getColumnSpan() ); + Iterator columnIterator = index.getColumnIterator(); + Column column = columnIterator.next(); + assertEquals( "summ_size", column.getName() ); + column = columnIterator.next(); + assertEquals( "text", column.getName() ); + assertSame( join.getTable(), index.getTable() ); + + } + + @Test + public void testCollectionTableIndex(){ + PersistentClass entity = configuration().getClassMapping( WealthyPerson.class.getName() ); + Property property = entity.getProperty( "explicitVacationHomes" ); + Set set = (Set)property.getValue(); + Table collectionTable = set.getCollectionTable(); + + Iterator itr = collectionTable.getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); + assertEquals( 2, index.getColumnSpan() ); + Iterator columnIterator = index.getColumnIterator(); + Column column = columnIterator.next(); + assertEquals( "countryName", column.getName() ); + column = columnIterator.next(); + assertEquals( "type_id", column.getName() ); + assertSame( collectionTable, index.getTable() ); + + } + + @Test + public void testJoinTableIndex(){ + PersistentClass entity = configuration().getClassMapping( Alias.class.getName() ); + Property property = entity.getProperty( "characters" ); + Bag set = (Bag)property.getValue(); + Table collectionTable = set.getCollectionTable(); + + Iterator itr = collectionTable.getIndexIterator(); + assertTrue( itr.hasNext() ); + Index index = itr.next(); + assertFalse( itr.hasNext() ); + assertTrue( "index name is not generated", StringHelper.isNotEmpty( index.getName() ) ); + assertEquals( 1, index.getColumnSpan() ); + Iterator columnIterator = index.getColumnIterator(); + Column column = columnIterator.next(); + assertEquals( "characters_id", column.getName() ); + assertSame( collectionTable, index.getTable() ); + } + + @Test + public void testTableGeneratorIndex(){ + //todo + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/SerializableToBlobTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/SerializableToBlobTypeTest.java index b452e70a4c..a037408c2e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/SerializableToBlobTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/SerializableToBlobTypeTest.java @@ -4,6 +4,8 @@ import org.junit.Test; import org.hibernate.Session; import org.hibernate.metamodel.spi.binding.EntityBinding; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.type.SerializableToBlobType; import org.hibernate.type.Type; @@ -15,6 +17,7 @@ import static org.junit.Assert.assertEquals; * * @author Janario Oliveira */ +@RequiresDialectFeature( DialectChecks.SupportsExpectedLobUsagePattern.class ) public class SerializableToBlobTypeTest extends BaseCoreFunctionalTestCase { @Test public void testTypeDefinition() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoonewithformula/ManyToOneWithFormulaTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoonewithformula/ManyToOneWithFormulaTest.java index 046e457269..ec11c6811f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoonewithformula/ManyToOneWithFormulaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoonewithformula/ManyToOneWithFormulaTest.java @@ -26,7 +26,8 @@ */ package org.hibernate.test.annotations.manytoonewithformula; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import org.hibernate.Session; import org.hibernate.Transaction; @@ -37,10 +38,9 @@ import org.hibernate.dialect.SQLServer2005Dialect; import org.hibernate.testing.FailureExpectedWithNewMetamodel; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.SkipForDialects; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.Test; /** * @author Sharath Reddy @@ -157,8 +157,11 @@ public class ManyToOneWithFormulaTest extends BaseCoreFunctionalTestCase { } @Test - @SkipForDialect(value = { HSQLDialect.class, SQLServer2005Dialect.class, Oracle8iDialect.class, DB2Dialect.class }, - comment = "The used join conditions does not work in HSQLDB. See HHH-4497. And oracle/db2 do not support 'substring' function") + @SkipForDialects( { + @SkipForDialect( value = { HSQLDialect.class }, comment = "The used join conditions does not work in HSQLDB. See HHH-4497." ), + @SkipForDialect( value = { SQLServer2005Dialect.class } ), + @SkipForDialect( value = { Oracle8iDialect.class }, comment = "Oracle/DB2 do not support 'substring' function" ), + @SkipForDialect( value = { DB2Dialect.class }, comment = "Oracle/DB2 do not support 'substring' function" ) } ) public void testManyToOneFromNonPkToNonPk() throws Exception { // also tests usage of the stand-alone @JoinFormula annotation (i.e. not wrapped within @JoinColumnsOrFormulas) Session s = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/BankAccount.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/BankAccount.java new file mode 100644 index 0000000000..f75cc75067 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/BankAccount.java @@ -0,0 +1,71 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.annotations.onetomany; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; + +/** + * @author Brett Meyer + */ +@Entity +public class BankAccount { + + @Id + @GeneratedValue + private long id; + + @OneToMany(mappedBy = "account", cascade = { CascadeType.ALL }) + @OrderColumn(name = "transactions_index") + private List transactions; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public List getTransactions() { + return transactions; + } + + public void setTransactions(List transactions) { + this.transactions = transactions; + } + + public void addTransaction(String code) { + if ( transactions == null ) { + transactions = new ArrayList(); + } + Transaction transaction = new Transaction(); + transaction.setCode( code ); + transactions.add( transaction ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Box.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Box.java new file mode 100644 index 0000000000..e30d850bfa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Box.java @@ -0,0 +1,67 @@ +package org.hibernate.test.annotations.onetomany; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +public class Box implements Serializable { + @Id + private int id; + + @OneToMany( mappedBy = "box" ) + @OrderBy( "sortField DESC, code" ) // Sorting by @Formula calculated field. + private List items = new ArrayList(); + + public Box() { + } + + public Box(int id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof Box ) ) return false; + + Box box = (Box) o; + + if ( id != box.id ) return false; + + return true; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "Box(id = " + id + ")"; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/DefaultNullOrderingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/DefaultNullOrderingTest.java new file mode 100644 index 0000000000..40842e6b61 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/DefaultNullOrderingTest.java @@ -0,0 +1,132 @@ +package org.hibernate.test.annotations.onetomany; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import org.hibernate.Criteria; +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-465") +@RequiresDialect(value = H2Dialect.class, + comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression.") +public class DefaultNullOrderingTest extends BaseCoreFunctionalTestCase { + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.DEFAULT_NULL_ORDERING, "last" ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Monkey.class, Troop.class, Soldier.class }; + } + + @Test + public void testHqlDefaultNullOrdering() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Monkey monkey1 = new Monkey(); + monkey1.setName( null ); + Monkey monkey2 = new Monkey(); + monkey2.setName( "Warsaw ZOO" ); + session.persist( monkey1 ); + session.persist( monkey2 ); + session.getTransaction().commit(); + + session.getTransaction().begin(); + List orderedResults = (List) session.createQuery( "from Monkey m order by m.name" ).list(); // Should order by NULLS LAST. + Assert.assertEquals( Arrays.asList( monkey2, monkey1 ), orderedResults ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( monkey1 ); + session.delete( monkey2 ); + session.getTransaction().commit(); + + session.close(); + } + + @Test + public void testAnnotationsDefaultNullOrdering() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Troop troop = new Troop(); + troop.setName( "Alpha 1" ); + Soldier ranger = new Soldier(); + ranger.setName( "Ranger 1" ); + troop.addSoldier( ranger ); + Soldier sniper = new Soldier(); + sniper.setName( null ); + troop.addSoldier( sniper ); + session.persist( troop ); + session.getTransaction().commit(); + + session.clear(); + + session.getTransaction().begin(); + troop = (Troop) session.get( Troop.class, troop.getId() ); + Iterator iterator = troop.getSoldiers().iterator(); // Should order by NULLS LAST. + Assert.assertEquals( ranger.getName(), iterator.next().getName() ); + Assert.assertNull( iterator.next().getName() ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( troop ); + session.getTransaction().commit(); + + session.close(); + } + + @Test + public void testCriteriaDefaultNullOrdering() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Monkey monkey1 = new Monkey(); + monkey1.setName( null ); + Monkey monkey2 = new Monkey(); + monkey2.setName( "Berlin ZOO" ); + session.persist( monkey1 ); + session.persist( monkey2 ); + session.getTransaction().commit(); + + session.getTransaction().begin(); + Criteria criteria = session.createCriteria( Monkey.class ); + criteria.addOrder( org.hibernate.criterion.Order.asc( "name" ) ); // Should order by NULLS LAST. + Assert.assertEquals( Arrays.asList( monkey2, monkey1 ), criteria.list() ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( monkey1 ); + session.delete( monkey2 ); + session.getTransaction().commit(); + + session.close(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Item.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Item.java new file mode 100644 index 0000000000..bfd4191f6e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Item.java @@ -0,0 +1,95 @@ +package org.hibernate.test.annotations.onetomany; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.annotations.Formula; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +public class Item implements Serializable { + @Id + private int id; + + @Column( name = "code" ) + private String code; + + @Formula( "( SELECT LENGTH( code ) FROM DUAL )" ) + private int sortField; + + @ManyToOne + private Box box; + + public Item() { + } + + public Item(int id, String code, Box box) { + this.id = id; + this.code = code; + this.box = box; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof Item ) ) return false; + + Item item = (Item) o; + + if ( id != item.id ) return false; + if ( sortField != item.sortField ) return false; + if ( code != null ? !code.equals( item.code ) : item.code != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + ( code != null ? code.hashCode() : 0 ); + result = 31 * result + sortField; + return result; + } + + @Override + public String toString() { + return "Item(id = " + id + ", code = " + code + ", sortField = " + sortField + ")"; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public int getSortField() { + return sortField; + } + + public void setSortField(int sortField) { + this.sortField = sortField; + } + + public Box getBox() { + return box; + } + + public void setBox(Box box) { + this.box = box; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java index bb06af5f77..a0eb252ab5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java @@ -23,16 +23,41 @@ */ package org.hibernate.test.annotations.onetomany; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.hibernate.Criteria; +import org.hibernate.NullPrecedence; import org.hibernate.Session; import org.hibernate.testing.FailureExpectedWithNewMetamodel; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.sql.SimpleSelect; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; +import org.junit.Assert; +import org.junit.Test; /** * @author Emmanuel Bernard + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + * @author Brett Meyer */ @FailureExpectedWithNewMetamodel public class OrderByTest extends BaseCoreFunctionalTestCase { @@ -72,8 +97,283 @@ public class OrderByTest extends BaseCoreFunctionalTestCase { s.close(); } + @Test + @TestForIssue(jiraKey = "HHH-465") + @RequiresDialect(value = { H2Dialect.class, MySQLDialect.class }, + comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " + + "For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " + + "MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.") + public void testAnnotationNullsFirstLast() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Tiger tiger1 = new Tiger(); + tiger1.setName( null ); // Explicitly setting null value. + Tiger tiger2 = new Tiger(); + tiger2.setName( "Max" ); + Monkey monkey1 = new Monkey(); + monkey1.setName( "Michael" ); + Monkey monkey2 = new Monkey(); + monkey2.setName( null ); // Explicitly setting null value. + Zoo zoo = new Zoo( "Warsaw ZOO" ); + zoo.getTigers().add( tiger1 ); + zoo.getTigers().add( tiger2 ); + zoo.getMonkeys().add( monkey1 ); + zoo.getMonkeys().add( monkey2 ); + session.persist( zoo ); + session.persist( tiger1 ); + session.persist( tiger2 ); + session.persist( monkey1 ); + session.persist( monkey2 ); + session.getTransaction().commit(); + + session.clear(); + + session.getTransaction().begin(); + zoo = (Zoo) session.get( Zoo.class, zoo.getId() ); + // Testing @org.hibernate.annotations.OrderBy. + Iterator iterator1 = zoo.getTigers().iterator(); + Assert.assertEquals( tiger2.getName(), iterator1.next().getName() ); + Assert.assertNull( iterator1.next().getName() ); + // Testing @javax.persistence.OrderBy. + Iterator iterator2 = zoo.getMonkeys().iterator(); + Assert.assertEquals( monkey1.getName(), iterator2.next().getName() ); + Assert.assertNull( iterator2.next().getName() ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( tiger1 ); + session.delete( tiger2 ); + session.delete( monkey1 ); + session.delete( monkey2 ); + session.delete( zoo ); + session.getTransaction().commit(); + + session.close(); + } + + @Test + @TestForIssue(jiraKey = "HHH-465") + @RequiresDialect(value = { H2Dialect.class, MySQLDialect.class }, + comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " + + "For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " + + "MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.") + public void testCriteriaNullsFirstLast() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Zoo zoo1 = new Zoo( null ); + Zoo zoo2 = new Zoo( "Warsaw ZOO" ); + session.persist( zoo1 ); + session.persist( zoo2 ); + session.getTransaction().commit(); + + session.clear(); + + session.getTransaction().begin(); + Criteria criteria = session.createCriteria( Zoo.class ); + criteria.addOrder( org.hibernate.criterion.Order.asc( "name" ).nulls( NullPrecedence.LAST ) ); + Iterator iterator = (Iterator) criteria.list().iterator(); + Assert.assertEquals( zoo2.getName(), iterator.next().getName() ); + Assert.assertNull( iterator.next().getName() ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( zoo1 ); + session.delete( zoo2 ); + session.getTransaction().commit(); + + session.close(); + } + + @Test + @TestForIssue(jiraKey = "HHH-465") + @RequiresDialect(value = { H2Dialect.class, MySQLDialect.class }, + comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " + + "For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " + + "MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.") + public void testNullsFirstLastSpawnMultipleColumns() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Zoo zoo = new Zoo(); + zoo.setName( "Berlin ZOO" ); + Visitor visitor1 = new Visitor( null, null ); + Visitor visitor2 = new Visitor( null, "Antoniak" ); + Visitor visitor3 = new Visitor( "Lukasz", "Antoniak" ); + zoo.getVisitors().add( visitor1 ); + zoo.getVisitors().add( visitor2 ); + zoo.getVisitors().add( visitor3 ); + session.save( zoo ); + session.save( visitor1 ); + session.save( visitor2 ); + session.save( visitor3 ); + session.getTransaction().commit(); + + session.clear(); + + session.getTransaction().begin(); + zoo = (Zoo) session.get( Zoo.class, zoo.getId() ); + Iterator iterator = zoo.getVisitors().iterator(); + Assert.assertEquals( 3, zoo.getVisitors().size() ); + Assert.assertEquals( visitor3, iterator.next() ); + Assert.assertEquals( visitor2, iterator.next() ); + Assert.assertEquals( visitor1, iterator.next() ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( visitor1 ); + session.delete( visitor2 ); + session.delete( visitor3 ); + session.delete( zoo ); + session.getTransaction().commit(); + + session.close(); + } + + @Test + @TestForIssue(jiraKey = "HHH-465") + @RequiresDialect(value = { H2Dialect.class, MySQLDialect.class }, + comment = "By default H2 places NULL values first, so testing 'NULLS LAST' expression. " + + "For MySQL testing overridden Dialect#renderOrderByElement(String, String, String, NullPrecedence) method. " + + "MySQL does not support NULLS FIRST / LAST syntax at the moment, so transforming the expression to 'CASE WHEN ...'.") + public void testHqlNullsFirstLast() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Zoo zoo1 = new Zoo(); + zoo1.setName( null ); + Zoo zoo2 = new Zoo(); + zoo2.setName( "Warsaw ZOO" ); + session.persist( zoo1 ); + session.persist( zoo2 ); + session.getTransaction().commit(); + + session.getTransaction().begin(); + List orderedResults = (List) session.createQuery( "from Zoo z order by z.name nulls lAsT" ).list(); + Assert.assertEquals( Arrays.asList( zoo2, zoo1 ), orderedResults ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( zoo1 ); + session.delete( zoo2 ); + session.getTransaction().commit(); + + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-7608" ) + @RequiresDialect({ H2Dialect.class, Oracle8iDialect.class }) + public void testOrderByReferencingFormulaColumn() { + Session session = openSession(); + + // Populating database with test data. + session.getTransaction().begin(); + Box box1 = new Box( 1 ); + Item item1 = new Item( 1, "1", box1 ); + Item item2 = new Item( 2, "22", box1 ); + Item item3 = new Item( 3, "2", box1 ); + session.persist( box1 ); + session.persist( item1 ); + session.persist( item2 ); + session.persist( item3 ); + session.flush(); + session.refresh( item1 ); + session.refresh( item2 ); + session.refresh( item3 ); + session.getTransaction().commit(); + + session.clear(); + + session.getTransaction().begin(); + box1 = (Box) session.get( Box.class, box1.getId() ); + Assert.assertEquals( Arrays.asList( item2, item1, item3 ), box1.getItems() ); + session.getTransaction().commit(); + + session.clear(); + + // Cleanup data. + session.getTransaction().begin(); + session.delete( item1 ); + session.delete( item2 ); + session.delete( item3 ); + session.delete( box1 ); + session.getTransaction().commit(); + + session.close(); + } + + @Test + @TestForIssue(jiraKey = "HHH-5732") + public void testInverseIndex() { + final CollectionPersister transactionsPersister = sessionFactory().getCollectionPersister( + BankAccount.class.getName() + ".transactions" ); + assertTrue( transactionsPersister.isInverse() ); + + Session s = openSession(); + s.getTransaction().begin(); + + BankAccount account = new BankAccount(); + account.addTransaction( "zzzzz" ); + account.addTransaction( "aaaaa" ); + account.addTransaction( "mmmmm" ); + s.save( account ); + s.getTransaction().commit(); + + s.close(); + + s = openSession(); + s.getTransaction().begin(); + + try { + final QueryableCollection queryableCollection = (QueryableCollection) transactionsPersister; + SimpleSelect select = new SimpleSelect( getDialect() ) + .setTableName( queryableCollection.getTableName() ) + .addColumn( "code" ) + .addColumn( "transactions_index" ); + PreparedStatement preparedStatement = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( select.toStatementString() ); + ResultSet resultSet = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( preparedStatement ); + Map valueMap = new HashMap(); + while ( resultSet.next() ) { + final String code = resultSet.getString( 1 ); + assertFalse( "code column was null", resultSet.wasNull() ); + final int indx = resultSet.getInt( 2 ); + assertFalse( "List index column was null", resultSet.wasNull() ); + valueMap.put( indx, code ); + } + assertEquals( 3, valueMap.size() ); + assertEquals( "zzzzz", valueMap.get( 0 ) ); + assertEquals( "aaaaa", valueMap.get( 1 ) ); + assertEquals( "mmmmm", valueMap.get( 2 ) ); + } + catch ( SQLException e ) { + fail(e.getMessage()); + } + } + @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Order.class, OrderItem.class }; + return new Class[] { + Order.class, OrderItem.class, Zoo.class, Tiger.class, + Monkey.class, Visitor.class, Box.class, Item.class, + BankAccount.class, Transaction.class + }; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Soldier.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Soldier.java index b0ac65a594..6df82f1b63 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Soldier.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Soldier.java @@ -44,18 +44,20 @@ public class Soldier { this.troop = troop; } + @Override public boolean equals(Object o) { if ( this == o ) return true; if ( !( o instanceof Soldier ) ) return false; final Soldier soldier = (Soldier) o; - if ( !name.equals( soldier.name ) ) return false; + if ( name != null ? !name.equals( soldier.name ) : soldier.name != null ) return false; return true; } + @Override public int hashCode() { - return name.hashCode(); + return name != null ? name.hashCode() : 0; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Transaction.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Transaction.java new file mode 100644 index 0000000000..e7626a1fa5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Transaction.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.annotations.onetomany; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Brett Meyer + */ +@Entity +// "Transaction" reserved in some Dialects +@Table( name = "BankTransaction" ) +public class Transaction { + + @Id + @GeneratedValue + private long id; + + private String code; + + @ManyToOne( cascade = { CascadeType.ALL } ) + private BankAccount account; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public BankAccount getAccount() { + return account; + } + + public void setAccount(BankAccount account) { + this.account = account; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Visitor.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Visitor.java new file mode 100644 index 0000000000..2dfc0030ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Visitor.java @@ -0,0 +1,79 @@ +package org.hibernate.test.annotations.onetomany; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +public class Visitor implements Serializable { + @Id + @GeneratedValue + private Long id; + + private String firstName; + + private String lastName; + + public Visitor() { + } + + public Visitor(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof Visitor) ) return false; + + Visitor visitor = (Visitor) o; + + if ( firstName != null ? !firstName.equals( visitor.firstName ) : visitor.firstName != null ) return false; + if ( id != null ? !id.equals( visitor.id ) : visitor.id != null ) return false; + if ( lastName != null ? !lastName.equals( visitor.lastName ) : visitor.lastName != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( firstName != null ? firstName.hashCode() : 0 ); + result = 31 * result + ( lastName != null ? lastName.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Visitor(id = " + id + ", firstName = " + firstName + ", lastName = " + lastName + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Zoo.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Zoo.java new file mode 100644 index 0000000000..0609e06890 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/Zoo.java @@ -0,0 +1,111 @@ +package org.hibernate.test.annotations.onetomany; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; + +/** + * Entity used to test {@code NULL} values ordering in SQL {@code ORDER BY} clause. + * Implementation note: By default H2 places {@code NULL} values first. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +public class Zoo implements Serializable { + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany + @JoinColumn(name = "zoo_id") + @org.hibernate.annotations.OrderBy(clause = "name asc nulls last") // By default H2 places NULL values first. + private Set tigers = new HashSet(); + + @OneToMany + @JoinColumn(name = "zoo_id") + @javax.persistence.OrderBy("name asc nulls last") // According to JPA specification this is illegal, but works in Hibernate. + private Set monkeys = new HashSet(); + + @OneToMany + @JoinColumn(name = "zoo_id") + @javax.persistence.OrderBy("lastName desc nulls last, firstName asc nulls LaSt") // Sorting by multiple columns. + private Set visitors = new HashSet(); + + public Zoo() { + } + + public Zoo(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof Zoo ) ) return false; + + Zoo zoo = (Zoo) o; + + if ( id != null ? !id.equals( zoo.id ) : zoo.id != null ) return false; + if ( name != null ? !name.equals( zoo.name ) : zoo.name != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( name != null ? name.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Zoo(id = " + id + ", name = " + name + ")"; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getTigers() { + return tigers; + } + + public void setTigers(Set tigers) { + this.tigers = tigers; + } + + public Set getMonkeys() { + return monkeys; + } + + public void setMonkeys(Set monkeys) { + this.monkeys = monkeys; + } + + public Set getVisitors() { + return visitors; + } + + public void setVisitors(Set visitors) { + this.visitors = visitors; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/quote/Person.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/quote/Person.java new file mode 100644 index 0000000000..0705a3117b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/quote/Person.java @@ -0,0 +1,56 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.annotations.quote; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * @author Brett Meyer + */ +@Entity +@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }) }) +public class Person { + @Id + @GeneratedValue + private long id; + + private String name; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/quote/QuoteGlobalTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/quote/QuoteGlobalTest.java index 029187b891..4b787ea61e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/quote/QuoteGlobalTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/quote/QuoteGlobalTest.java @@ -23,22 +23,42 @@ */ package org.hibernate.test.annotations.quote; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Iterator; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.testing.FailureExpectedWithNewMetamodel; +import org.hibernate.mapping.UniqueKey; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; +import org.junit.Test; /** * @author Emmanuel Bernard + * @author Brett Meyer */ @FailureExpectedWithNewMetamodel public class QuoteGlobalTest extends BaseCoreFunctionalTestCase { + + @Test + @TestForIssue(jiraKey = "HHH-7890") + public void testQuotedUniqueConstraint() { + Iterator itr = configuration().getClassMapping( Person.class.getName() ) + .getTable().getUniqueKeyIterator(); + while ( itr.hasNext() ) { + UniqueKey uk = itr.next(); + assertEquals( uk.getColumns().size(), 1 ); + assertEquals( uk.getColumn( 0 ).getName(), "name"); + return; + } + fail( "GLOBALLY_QUOTED_IDENTIFIERS caused the unique key creation to fail." ); + } + @Test public void testQuoteManytoMany() { Session s = openSession(); @@ -69,7 +89,8 @@ public class QuoteGlobalTest extends BaseCoreFunctionalTestCase { return new Class[] { User.class, Role.class, - Phone.class + Phone.class, + Person.class }; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintTest.java index 4c3c3b9a33..38f7b0e543 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintTest.java @@ -1,11 +1,21 @@ package org.hibernate.test.annotations.uniqueconstraint; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; +import java.util.Iterator; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + import org.hibernate.JDBCException; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.testing.FailureExpectedWithNewMetamodel; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -20,7 +30,9 @@ public class UniqueConstraintTest extends BaseCoreFunctionalTestCase { return new Class[]{ Room.class, Building.class, - House.class + House.class, + UniqueNoNameA.class, + UniqueNoNameB.class }; } @@ -55,5 +67,50 @@ public class UniqueConstraintTest extends BaseCoreFunctionalTestCase { tx.rollback(); s.close(); } - + @Test + @TestForIssue( jiraKey = "HHH-8026" ) + public void testUnNamedConstraints() { + Iterator iterator = configuration().getTableMappings(); + org.hibernate.mapping.Table tableA = null; + org.hibernate.mapping.Table tableB = null; + while( iterator.hasNext() ) { + org.hibernate.mapping.Table table = iterator.next(); + if ( table.getName().equals( "UniqueNoNameA" ) ) { + tableA = table; + } + else if ( table.getName().equals( "UniqueNoNameB" ) ) { + tableB = table; + } + } + + if ( tableA == null || tableB == null ) { + fail( "Could not find the expected tables." ); + } + + assertFalse( tableA.getUniqueKeyIterator().next().getName().equals( + tableB.getUniqueKeyIterator().next().getName() ) ); + } + + @Entity + @Table( name = "UniqueNoNameA", + uniqueConstraints = {@UniqueConstraint(columnNames={"name"})}) + public static class UniqueNoNameA { + @Id + @GeneratedValue + public long id; + + public String name; + } + + @Entity + @Table( name = "UniqueNoNameB", + uniqueConstraints = {@UniqueConstraint(columnNames={"name"})}) + public static class UniqueNoNameB { + @Id + @GeneratedValue + public long id; + + public String name; + } + } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintValidationTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintValidationTest.java new file mode 100644 index 0000000000..d6c8229201 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintValidationTest.java @@ -0,0 +1,69 @@ +package org.hibernate.test.annotations.uniqueconstraint; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.AnnotationException; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +/** + * @author Nikolay Shestakov + * + */ +public class UniqueConstraintValidationTest extends BaseUnitTestCase { + + @Test(expected = AnnotationException.class) + @TestForIssue(jiraKey = "HHH-4084") + public void testUniqueConstraintWithEmptyColumnName() { + buildSessionFactory(EmptyColumnNameEntity.class); + } + + @Test + public void testUniqueConstraintWithEmptyColumnNameList() { + buildSessionFactory(EmptyColumnNameListEntity.class); + } + + @Test(expected = AnnotationException.class) + public void testUniqueConstraintWithNotExistsColumnName() { + buildSessionFactory(NotExistsColumnEntity.class); + } + + private void buildSessionFactory(Class entity) { + Configuration cfg = new Configuration(); + cfg.addAnnotatedClass(entity); + cfg.buildMappings(); + ServiceRegistryImplementor serviceRegistry = (ServiceRegistryImplementor) new StandardServiceRegistryBuilder() + .applySettings(cfg.getProperties()).build(); + cfg.buildSessionFactory(serviceRegistry); + } + + @Entity + @Table(name = "tbl_emptycolumnnameentity", uniqueConstraints = @UniqueConstraint(columnNames = "")) + public static class EmptyColumnNameEntity implements Serializable { + @Id + protected Long id; + } + + @Entity + @Table(name = "tbl_emptycolumnnamelistentity", uniqueConstraints = @UniqueConstraint(columnNames = {})) + public static class EmptyColumnNameListEntity implements Serializable { + @Id + protected Long id; + } + + @Entity + @Table(name = "tbl_notexistscolumnentity", uniqueConstraints = @UniqueConstraint(columnNames = "notExists")) + public static class NotExistsColumnEntity implements Serializable { + @Id + protected Long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batch/BatchTest.java b/hibernate-core/src/test/java/org/hibernate/test/batch/BatchTest.java index 067826affd..3ec0d8dcee 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/batch/BatchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/batch/BatchTest.java @@ -64,27 +64,27 @@ public class BatchTest extends BaseCoreFunctionalTestCase { final int N = 5000; //26 secs with batch flush, 26 without //final int N = 100000; //53 secs with batch flush, OOME without //final int N = 250000; //137 secs with batch flush, OOME without - int batchSize = ( ( SessionFactoryImplementor ) sessionFactory() ).getSettings().getJdbcBatchSize(); + int batchSize = sessionFactory().getSettings().getJdbcBatchSize(); doBatchInsertUpdate( N, batchSize ); System.out.println( System.currentTimeMillis() - start ); } @Test public void testBatchInsertUpdateSizeEqJdbcBatchSize() { - int batchSize = ( ( SessionFactoryImplementor ) sessionFactory() ).getSettings().getJdbcBatchSize(); + int batchSize = sessionFactory().getSettings().getJdbcBatchSize(); doBatchInsertUpdate( 50, batchSize ); } @Test public void testBatchInsertUpdateSizeLtJdbcBatchSize() { - int batchSize = ( ( SessionFactoryImplementor ) sessionFactory() ).getSettings().getJdbcBatchSize(); + int batchSize = sessionFactory().getSettings().getJdbcBatchSize(); doBatchInsertUpdate( 50, batchSize - 1 ); } @Test public void testBatchInsertUpdateSizeGtJdbcBatchSize() { long start = System.currentTimeMillis(); - int batchSize = ( ( SessionFactoryImplementor ) sessionFactory() ).getSettings().getJdbcBatchSize(); + int batchSize = sessionFactory().getSettings().getJdbcBatchSize(); doBatchInsertUpdate( 50, batchSize + 1 ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java new file mode 100644 index 0000000000..42d10e1c66 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java @@ -0,0 +1,308 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.bytecode.enhancement; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.Method; +import java.util.Arrays; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtField; +import javassist.LoaderClassPath; + +import org.hibernate.EntityMode; +import org.hibernate.LockMode; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.cfg.Configuration; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.Status; +import org.hibernate.mapping.PersistentClass; + +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Steve Ebersole + */ +public class EnhancerTest extends BaseUnitTestCase { + private static EnhancementContext enhancementContext = new EnhancementContext() { + @Override + public ClassLoader getLoadingClassLoader() { + return getClass().getClassLoader(); + } + + @Override + public boolean isEntityClass(CtClass classDescriptor) { + return true; + } + + @Override + public boolean isCompositeClass(CtClass classDescriptor) { + return false; + } + + @Override + public boolean doDirtyCheckingInline(CtClass classDescriptor) { + return true; + } + + @Override + public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { + return true; + } + + @Override + public boolean isLazyLoadable(CtField field) { + return true; + } + + @Override + public boolean isPersistentField(CtField ctField) { + return true; + } + + @Override + public CtField[] order(CtField[] persistentFields) { + return persistentFields; + } + }; + + @Test + public void testEnhancement() throws Exception { + testFor( SimpleEntity.class ); + testFor( SubEntity.class ); + } + + private void testFor(Class entityClassToEnhance) throws Exception { + Enhancer enhancer = new Enhancer( enhancementContext ); + CtClass entityCtClass = generateCtClassForAnEntity( entityClassToEnhance ); + byte[] original = entityCtClass.toBytecode(); + byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original ); + assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) ); + + ClassLoader cl = new ClassLoader() { }; + ClassPool cp = new ClassPool( false ); + cp.appendClassPath( new LoaderClassPath( cl ) ); + CtClass enhancedCtClass = cp.makeClass( new ByteArrayInputStream( enhanced ) ); + Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() ); + Object entityInstance = entityClass.newInstance(); + + assertTyping( ManagedEntity.class, entityInstance ); + + // call the new methods + // + Method setter = entityClass.getMethod( Enhancer.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class ); + Method getter = entityClass.getMethod( Enhancer.ENTITY_ENTRY_GETTER_NAME ); + assertNull( getter.invoke( entityInstance ) ); + setter.invoke( entityInstance, makeEntityEntry() ); + assertNotNull( getter.invoke( entityInstance ) ); + setter.invoke( entityInstance, new Object[] {null} ); + assertNull( getter.invoke( entityInstance ) ); + + Method entityInstanceGetter = entityClass.getMethod( Enhancer.ENTITY_INSTANCE_GETTER_NAME ); + assertSame( entityInstance, entityInstanceGetter.invoke( entityInstance ) ); + + Method previousGetter = entityClass.getMethod( Enhancer.PREVIOUS_GETTER_NAME ); + Method previousSetter = entityClass.getMethod( Enhancer.PREVIOUS_SETTER_NAME, ManagedEntity.class ); + previousSetter.invoke( entityInstance, entityInstance ); + assertSame( entityInstance, previousGetter.invoke( entityInstance ) ); + + Method nextGetter = entityClass.getMethod( Enhancer.PREVIOUS_GETTER_NAME ); + Method nextSetter = entityClass.getMethod( Enhancer.PREVIOUS_SETTER_NAME, ManagedEntity.class ); + nextSetter.invoke( entityInstance, entityInstance ); + assertSame( entityInstance, nextGetter.invoke( entityInstance ) ); + + // add an attribute interceptor... + Method interceptorGetter = entityClass.getMethod( Enhancer.INTERCEPTOR_GETTER_NAME ); + Method interceptorSetter = entityClass.getMethod( Enhancer.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class ); + + assertNull( interceptorGetter.invoke( entityInstance ) ); + entityClass.getMethod( "getId" ).invoke( entityInstance ); + + interceptorSetter.invoke( entityInstance, new LocalPersistentAttributeInterceptor() ); + assertNotNull( interceptorGetter.invoke( entityInstance ) ); + + // dirty checking is unfortunately just printlns for now... just verify the test output + entityClass.getMethod( "getId" ).invoke( entityInstance ); + entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, entityClass.getMethod( "getId" ).invoke( entityInstance ) ); + entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, 1L ); + + entityClass.getMethod( "isActive" ).invoke( entityInstance ); + entityClass.getMethod( "setActive", boolean.class ).invoke( entityInstance, entityClass.getMethod( "isActive" ).invoke( entityInstance ) ); + entityClass.getMethod( "setActive", boolean.class ).invoke( entityInstance, true ); + + entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance ); + entityClass.getMethod( "setSomeNumber", long.class ).invoke( entityInstance, entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance ) ); + entityClass.getMethod( "setSomeNumber", long.class ).invoke( entityInstance, 1L ); + } + + private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception { + ClassPool cp = new ClassPool( false ); + return cp.makeClass( + getClass().getClassLoader().getResourceAsStream( + entityClassToEnhance.getName().replace( '.', '/' ) + ".class" + ) + ); + } + + private EntityEntry makeEntityEntry() { + return new EntityEntry( + Status.MANAGED, + null, + null, + new Long(1), + null, + LockMode.NONE, + false, + null, + EntityMode.POJO, + null, + false, + false, + null + ); + } + + + private class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor { + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { + System.out.println( "Reading boolean [" + name + "]" ); + return oldValue; + } + + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + System.out.println( "Writing boolean [" + name + "]" ); + return newValue; + } + + @Override + public byte readByte(Object obj, String name, byte oldValue) { + System.out.println( "Reading byte [" + name + "]" ); + return oldValue; + } + + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + System.out.println( "Writing byte [" + name + "]" ); + return newValue; + } + + @Override + public char readChar(Object obj, String name, char oldValue) { + System.out.println( "Reading char [" + name + "]" ); + return oldValue; + } + + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { + System.out.println( "Writing char [" + name + "]" ); + return newValue; + } + + @Override + public short readShort(Object obj, String name, short oldValue) { + System.out.println( "Reading short [" + name + "]" ); + return oldValue; + } + + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { + System.out.println( "Writing short [" + name + "]" ); + return newValue; + } + + @Override + public int readInt(Object obj, String name, int oldValue) { + System.out.println( "Reading int [" + name + "]" ); + return oldValue; + } + + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { + System.out.println( "Writing int [" + name + "]" ); + return newValue; + } + + @Override + public float readFloat(Object obj, String name, float oldValue) { + System.out.println( "Reading float [" + name + "]" ); + return oldValue; + } + + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { + System.out.println( "Writing float [" + name + "]" ); + return newValue; + } + + @Override + public double readDouble(Object obj, String name, double oldValue) { + System.out.println( "Reading double [" + name + "]" ); + return oldValue; + } + + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { + System.out.println( "Writing double [" + name + "]" ); + return newValue; + } + + @Override + public long readLong(Object obj, String name, long oldValue) { + System.out.println( "Reading long [" + name + "]" ); + return oldValue; + } + + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { + System.out.println( "Writing long [" + name + "]" ); + return newValue; + } + + @Override + public Object readObject(Object obj, String name, Object oldValue) { + System.out.println( "Reading Object [" + name + "]" ); + return oldValue; + } + + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + System.out.println( "Writing Object [" + name + "]" ); + return newValue; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java new file mode 100644 index 0000000000..8579c413b2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java @@ -0,0 +1,79 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.bytecode.enhancement; + +import org.hibernate.Session; + +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Steve Ebersole + */ +public class MostBasicEnhancementTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { MyEntity.class }; + } + + @Test + public void testIt() { + Session s = openSession(); + s.beginTransaction(); + s.save( new MyEntity( 1L ) ); + s.save( new MyEntity( 2L ) ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + MyEntity myEntity = (MyEntity) s.get( MyEntity.class, 1L ); + MyEntity myEntity2 = (MyEntity) s.get( MyEntity.class, 2L ); + + assertNotNull( myEntity.$$_hibernate_getEntityInstance() ); + assertSame( myEntity, myEntity.$$_hibernate_getEntityInstance() ); + assertNotNull( myEntity.$$_hibernate_getEntityEntry() ); + assertNull( myEntity.$$_hibernate_getPreviousManagedEntity() ); + assertNotNull( myEntity.$$_hibernate_getNextManagedEntity() ); + + assertNotNull( myEntity2.$$_hibernate_getEntityInstance() ); + assertSame( myEntity2, myEntity2.$$_hibernate_getEntityInstance() ); + assertNotNull( myEntity2.$$_hibernate_getEntityEntry() ); + assertNotNull( myEntity2.$$_hibernate_getPreviousManagedEntity() ); + assertNull( myEntity2.$$_hibernate_getNextManagedEntity() ); + + s.createQuery( "delete MyEntity" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + + assertNull( myEntity.$$_hibernate_getEntityEntry() ); + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java new file mode 100644 index 0000000000..a784044d89 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java @@ -0,0 +1,106 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.bytecode.enhancement; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Transient; + +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.EntityEntry; + +/** + * @author Steve Ebersole + */ +@Entity +public class MyEntity implements ManagedEntity { + @Transient + private transient EntityEntry entityEntry; + @Transient + private transient ManagedEntity previous; + @Transient + private transient ManagedEntity next; + + private Long id; + private String name; + + public MyEntity() { + } + + public MyEntity(Long id) { + this.id = id; + } + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public Object $$_hibernate_getEntityInstance() { + return this; + } + + @Override + public EntityEntry $$_hibernate_getEntityEntry() { + return entityEntry; + } + + @Override + public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) { + this.entityEntry = entityEntry; + } + + @Override + public ManagedEntity $$_hibernate_getNextManagedEntity() { + return next; + } + + @Override + public void $$_hibernate_setNextManagedEntity(ManagedEntity next) { + this.next = next; + } + + @Override + public ManagedEntity $$_hibernate_getPreviousManagedEntity() { + return previous; + } + + @Override + public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) { + this.previous = previous; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SampleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SampleEntity.java new file mode 100644 index 0000000000..7681e53288 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SampleEntity.java @@ -0,0 +1,141 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.bytecode.enhancement; + +import javax.persistence.Id; +import javax.persistence.Transient; + +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; + +/** + * @author Steve Ebersole + */ +public class SampleEntity implements ManagedEntity, PersistentAttributeInterceptable { + @Transient + private transient EntityEntry entityEntry; + @Transient + private transient ManagedEntity previous; + @Transient + private transient ManagedEntity next; + @Transient + private transient PersistentAttributeInterceptor interceptor; + + private Long id; + private String name; + + @Id + public Long getId() { + return hibernate_read_id(); + } + + public void setId(Long id) { + hibernate_write_id( id ); + } + + public String getName() { + return hibernate_read_name(); + } + + public void setName(String name) { + hibernate_write_name( name ); + } + + private Long hibernate_read_id() { + if ( $$_hibernate_getInterceptor() != null ) { + this.id = (Long) $$_hibernate_getInterceptor().readObject( this, "id", this.id ); + } + return id; + } + + private void hibernate_write_id(Long id) { + Long localVar = id; + if ( $$_hibernate_getInterceptor() != null ) { + localVar = (Long) $$_hibernate_getInterceptor().writeObject( this, "id", this.id, id ); + } + this.id = localVar; + } + + private String hibernate_read_name() { + if ( $$_hibernate_getInterceptor() != null ) { + this.name = (String) $$_hibernate_getInterceptor().readObject( this, "name", this.name ); + } + return name; + } + + private void hibernate_write_name(String name) { + String localName = name; + if ( $$_hibernate_getInterceptor() != null ) { + localName = (String) $$_hibernate_getInterceptor().writeObject( this, "name", this.name, name ); + } + this.name = localName; + } + + @Override + public Object $$_hibernate_getEntityInstance() { + return this; + } + + @Override + public EntityEntry $$_hibernate_getEntityEntry() { + return entityEntry; + } + + @Override + public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) { + this.entityEntry = entityEntry; + } + + @Override + public ManagedEntity $$_hibernate_getNextManagedEntity() { + return next; + } + + @Override + public void $$_hibernate_setNextManagedEntity(ManagedEntity next) { + this.next = next; + } + + @Override + public ManagedEntity $$_hibernate_getPreviousManagedEntity() { + return previous; + } + + @Override + public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) { + this.previous = previous; + } + + @Override + public PersistentAttributeInterceptor $$_hibernate_getInterceptor() { + return interceptor; + } + + @Override + public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor interceptor) { + this.interceptor = interceptor; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SimpleEntity.java new file mode 100644 index 0000000000..158d3a4fec --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SimpleEntity.java @@ -0,0 +1,71 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.bytecode.enhancement; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class SimpleEntity { + private Long id; + private String name; + private boolean active; + private long someNumber; + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public long getSomeNumber() { + return someNumber; + } + + public void setSomeNumber(long someNumber) { + this.someNumber = someNumber; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java new file mode 100644 index 0000000000..342eddabec --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java @@ -0,0 +1,71 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.bytecode.enhancement; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class SubEntity extends SuperEntity { + private Long id; + private String name; + private boolean active; + private long someNumber; + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public long getSomeNumber() { + return someNumber; + } + + public void setSomeNumber(long someNumber) { + this.someNumber = someNumber; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureReturn.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java similarity index 90% rename from hibernate-core/src/main/java/org/hibernate/StoredProcedureReturn.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java index d14c4e11ad..8306fa97ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/StoredProcedureReturn.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java @@ -21,11 +21,13 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate; +package org.hibernate.test.bytecode.enhancement; + +import javax.persistence.Entity; /** * @author Steve Ebersole */ -public interface StoredProcedureReturn { - public boolean isResultSet(); +@Entity +public class SuperEntity { } diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/RefreshTest.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/RefreshTest.java index 3e065ce576..ba73ce98c1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cascade/RefreshTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/RefreshTest.java @@ -22,20 +22,20 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.test.cascade; +import static org.junit.Assert.assertEquals; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Date; import java.util.Iterator; -import org.junit.Test; - import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; +import org.junit.Test; /** * Implementation of RefreshTest. @@ -62,7 +62,7 @@ public class RefreshTest extends BaseCoreFunctionalTestCase { session.flush(); // behind the session's back, let's modify the statuses - updateStatuses( session ); + updateStatuses( (SessionImplementor)session ); // Now lets refresh the persistent batch, and see if the refresh cascaded to the jobs collection elements session.refresh( batch ); @@ -77,20 +77,20 @@ public class RefreshTest extends BaseCoreFunctionalTestCase { session.close(); } - private void updateStatuses(Session session) throws Throwable { - session.doWork( + private void updateStatuses(final SessionImplementor session) throws Throwable { + ((Session)session).doWork( new Work() { @Override public void execute(Connection connection) throws SQLException { PreparedStatement stmnt = null; try { - stmnt = connection.prepareStatement( "UPDATE T_JOB SET JOB_STATUS = 1" ); - stmnt.executeUpdate(); + stmnt = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( "UPDATE T_JOB SET JOB_STATUS = 1" ); + session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( stmnt ); } finally { if ( stmnt != null ) { try { - stmnt.close(); + session.getTransactionCoordinator().getJdbcCoordinator().release( stmnt ); } catch( Throwable ignore ) { } diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/Route.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/Route.java index 23a368162b..fe9a0e8d76 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/Route.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/circle/Route.java @@ -96,11 +96,11 @@ public class Route { buffer.append("Route name: " + name + " id: " + routeID + " transientField: " + transientField + "\n"); for (Iterator it = nodes.iterator(); it.hasNext();) { - buffer.append("Node: " + (Node)it.next()); + buffer.append("Node: " + it.next() ); } for (Iterator it = vehicles.iterator(); it.hasNext();) { - buffer.append("Vehicle: " + (Vehicle)it.next()); + buffer.append("Vehicle: " + it.next() ); } return buffer.toString(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/list/LineItem.java b/hibernate-core/src/test/java/org/hibernate/test/collection/list/LineItem.java new file mode 100644 index 0000000000..768dd19cfc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/list/LineItem.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.collection.list; + + +/** + * @author Steve Ebersole + */ +public class LineItem { + private Integer id; + private Order order; + private String productCode; + private int quantity; + + public LineItem() { + } + + public LineItem(Order order, String productCode, int quantity) { + this.order = order; + this.productCode = productCode; + this.quantity = quantity; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } + + public String getProductCode() { + return productCode; + } + + public void setProductCode(String productCode) { + this.productCode = productCode; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/list/Order.java b/hibernate-core/src/test/java/org/hibernate/test/collection/list/Order.java new file mode 100644 index 0000000000..7b88f173bc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/list/Order.java @@ -0,0 +1,73 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.collection.list; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Steve Ebersole + */ +public class Order { + private Integer id; + private String code; + private List lineItems = new ArrayList(); + + public Order() { + } + + public Order(String code) { + this.code = code; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public List getLineItems() { + return lineItems; + } + + public void setLineItems(List lineItems) { + this.lineItems = lineItems; + } + + public LineItem addLineItem(String productCode, int quantity) { + LineItem lineItem = new LineItem( this, productCode, quantity ); + lineItems.add( lineItem ); + return lineItem; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java index 70447ed22a..2650769820 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java @@ -23,6 +23,10 @@ */ package org.hibernate.test.collection.list; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -31,21 +35,16 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import org.junit.Test; - import org.hibernate.Session; import org.hibernate.collection.internal.PersistentList; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.sql.SimpleSelect; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.Test; /** * Tests related to operations on a PersistentList @@ -53,6 +52,7 @@ import static org.junit.Assert.assertTrue; * @author Steve Ebersole */ public class PersistentListTest extends BaseCoreFunctionalTestCase { + @Override public String[] getMappings() { return new String[] { "collection/list/Mappings.hbm.xml" }; @@ -60,8 +60,7 @@ public class PersistentListTest extends BaseCoreFunctionalTestCase { @Test @TestForIssue( jiraKey = "HHH-5732" ) - @FailureExpected( jiraKey = "HHH-5732" ) - public void testInverseListIndexColumnWritten() { + public void testInverseListIndex() { // make sure no one changes the mapping final CollectionPersister collectionPersister = sessionFactory().getCollectionPersister( ListOwner.class.getName() + ".children" ); assertTrue( collectionPersister.isInverse() ); @@ -83,9 +82,9 @@ public class PersistentListTest extends BaseCoreFunctionalTestCase { session.close(); // now, make sure the list-index column gotten written... - session = openSession(); - session.beginTransaction(); - session.doWork( + final Session session2 = openSession(); + session2.beginTransaction(); + session2.doWork( new Work() { @Override public void execute(Connection connection) throws SQLException { @@ -95,9 +94,9 @@ public class PersistentListTest extends BaseCoreFunctionalTestCase { .addColumn( "NAME" ) .addColumn( "LIST_INDEX" ) .addCondition( "NAME", "<>", "?" ); - PreparedStatement preparedStatement = connection.prepareStatement( select.toStatementString() ); + PreparedStatement preparedStatement = ((SessionImplementor)session2).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( select.toStatementString() ); preparedStatement.setString( 1, "root" ); - ResultSet resultSet = preparedStatement.executeQuery(); + ResultSet resultSet = ((SessionImplementor)session2).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( preparedStatement ); Map valueMap = new HashMap(); while ( resultSet.next() ) { final String name = resultSet.getString( 1 ); @@ -115,9 +114,65 @@ public class PersistentListTest extends BaseCoreFunctionalTestCase { } } ); - session.delete( root ); + session2.delete( root ); + session2.getTransaction().commit(); + session2.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-5732" ) + public void testInverseListIndex2() { + // make sure no one changes the mapping + final CollectionPersister collectionPersister = sessionFactory().getCollectionPersister( Order.class.getName() + ".lineItems" ); + assertTrue( collectionPersister.isInverse() ); + + // do some creations... + Session session = openSession(); + session.beginTransaction(); + + Order order = new Order( "acme-1" ); + order.addLineItem( "abc", 2 ); + order.addLineItem( "def", 200 ); + order.addLineItem( "ghi", 13 ); + session.save( order ); session.getTransaction().commit(); session.close(); + + // now, make sure the list-index column gotten written... + final Session session2 = openSession(); + session2.beginTransaction(); + session2.doWork( + new Work() { + @Override + public void execute(Connection connection) throws SQLException { + final QueryableCollection queryableCollection = (QueryableCollection) collectionPersister; + SimpleSelect select = new SimpleSelect( getDialect() ) + .setTableName( queryableCollection.getTableName() ) + .addColumn( "ORDER_ID" ) + .addColumn( "INDX" ) + .addColumn( "PRD_CODE" ); + PreparedStatement preparedStatement = ((SessionImplementor)session2).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( select.toStatementString() ); + ResultSet resultSet = ((SessionImplementor)session2).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( preparedStatement ); + Map valueMap = new HashMap(); + while ( resultSet.next() ) { + final int fk = resultSet.getInt( 1 ); + assertFalse( "Collection key (FK) column was null", resultSet.wasNull() ); + final int indx = resultSet.getInt( 2 ); + assertFalse( "List index column was null", resultSet.wasNull() ); + final String prodCode = resultSet.getString( 3 ); + assertFalse( "Prod code column was null", resultSet.wasNull() ); + valueMap.put( prodCode, indx ); + } + assertEquals( 3, valueMap.size() ); + assertEquals( Integer.valueOf( 0 ), valueMap.get( "abc" ) ); + assertEquals( Integer.valueOf( 1 ), valueMap.get( "def" ) ); + assertEquals( Integer.valueOf( 2 ), valueMap.get( "ghi" ) ); + } + } + ); + session2.delete( order ); + session2.getTransaction().commit(); + session2.close(); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java b/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java index 741f1bae7b..0f289fa0dd 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java @@ -78,7 +78,7 @@ public class ComponentTest extends BaseCoreFunctionalTestCase { Component component = ( Component ) personProperty.getValue(); Formula f = ( Formula ) component.getProperty( "yob" ).getValue().getColumnIterator().next(); - SQLFunction yearFunction = ( SQLFunction ) dialect.getFunctions().get( "year" ); + SQLFunction yearFunction = dialect.getFunctions().get( "year" ); if ( yearFunction == null ) { // the dialect not know to support a year() function, so rely on the // ANSI SQL extract function diff --git a/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java index ed175aa7f5..1b333bf180 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/criteria/CriteriaQueryTest.java @@ -1389,7 +1389,7 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { s.flush(); - List enrolments = ( List ) s.createCriteria( Enrolment.class).setProjection( Projections.id() ).list(); + List enrolments = s.createCriteria( Enrolment.class).setProjection( Projections.id() ).list(); t.rollback(); s.close(); } @@ -1406,7 +1406,7 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { s.save(course); s.flush(); s.clear(); - List data = ( List ) s.createCriteria( CourseMeeting.class).setProjection( Projections.id() ).list(); + List data = s.createCriteria( CourseMeeting.class).setProjection( Projections.id() ).list(); t.commit(); s.close(); @@ -1464,7 +1464,7 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { s.save(course); s.flush(); - List data = ( List ) s.createCriteria( CourseMeeting.class).setProjection( Projections.id().as( "id" ) ).list(); + List data = s.createCriteria( CourseMeeting.class).setProjection( Projections.id().as( "id" ) ).list(); t.rollback(); s.close(); } @@ -1481,7 +1481,7 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { s.save( gaith ); s.flush(); - List cityStates = ( List ) s.createCriteria( Student.class).setProjection( Projections.property( "cityState" ) ).list(); + List cityStates = s.createCriteria( Student.class).setProjection( Projections.property( "cityState" ) ).list(); t.rollback(); s.close(); } @@ -1497,7 +1497,7 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { gaith.setCityState( new CityState( "Chicago", "Illinois" ) ); s.save(gaith); s.flush(); - List data = ( List ) s.createCriteria( Student.class) + List data = s.createCriteria( Student.class) .setProjection( Projections.projectionList() .add( Projections.property( "cityState" ) ) .add( Projections.property("name") ) ) @@ -1546,7 +1546,7 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { gavin.getEnrolments().add(enrolment); s.save(enrolment); s.flush(); - List data = ( List ) s.createCriteria( Enrolment.class) + List data = s.createCriteria( Enrolment.class) .setProjection( Projections.projectionList() .add( Projections.property( "semester" ) ) .add( Projections.property("year") ) @@ -2012,6 +2012,67 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { session.close(); } + + @Test + @TestForIssue( jiraKey = "HHH-6643" ) + public void testNotNot() { + Student student1 = new Student(); + student1.setName("Foo1 Foo1"); + student1.setStudentNumber(1); + Student student2 = new Student(); + student2.setName("Foo2 Foo2"); + student2.setStudentNumber(2); + + Session s = openSession(); + Transaction t = s.beginTransaction(); + + s.persist( student1 ); + s.persist( student2 ); + s.flush(); + s.clear(); + + // Although this example is simplified and the "not not" is pointless, + // double negatives can occur in some dynamic applications (regardless + // if it results from bad design or not). Test to ensure the dialect + // handles them as expected. + List students = s.createCriteria( Student.class ).add( + Restrictions.not( + Restrictions.not( + Restrictions.eq( "studentNumber", 1l ) ) ) + ).list(); + + assertEquals( students.size(), 1 ); + assertEquals( students.get( 0 ).getStudentNumber(), 1 ); + + t.rollback(); + s.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-2951" ) + public void testNullCriteria() { + Course course = new Course(); + course.setCourseCode( "1234" ); + course.setDescription( null ); + + Session s = openSession(); + Transaction t = s.beginTransaction(); + + s.persist( course ); + s.flush(); + s.clear(); + + // Ensure Restrictions creates "where foo is null", instead of + // "where foo = null" + List courses = s.createCriteria( Course.class ).add( + Restrictions.eqOrIsNull( "description", null) ).list(); + + assertEquals( courses.size(), 1 ); + assertEquals( courses.get( 0 ).getCourseCode(), course.getCourseCode() ); + + t.rollback(); + s.close(); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java b/hibernate-core/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java index 7c02a7c3f9..de0e913dc8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java @@ -289,11 +289,11 @@ public class OuterJoinCriteriaTest extends BaseCoreFunctionalTestCase { Order o = (Order) it.next(); if ( order1.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); - assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else if ( order3.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); - assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else { fail( "unknown order" ); @@ -332,7 +332,7 @@ public class OuterJoinCriteriaTest extends BaseCoreFunctionalTestCase { for ( Object order : orders ) { Order o = (Order) order; if ( order1.getOrderId() == o.getOrderId() ) { - assertEquals( "1000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + assertEquals( "1000", o.getLines().iterator().next().getArticleId() ); } else if ( order2.getOrderId() == o.getOrderId() ) { assertEquals( 0, o.getLines().size() ); @@ -367,11 +367,11 @@ public class OuterJoinCriteriaTest extends BaseCoreFunctionalTestCase { Order o = (Order) it.next(); if ( order1.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); - assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else if ( order3.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); - assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else { fail( "unknown order" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java index 1aecb079dd..e0a7c3be92 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java @@ -22,12 +22,12 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.test.cut; +import static org.junit.Assert.assertEquals; + import java.math.BigDecimal; import java.util.Currency; import java.util.List; -import org.junit.Test; - import org.hibernate.Session; import org.hibernate.criterion.Restrictions; import org.hibernate.dialect.DB2Dialect; @@ -35,9 +35,9 @@ import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.testing.FailureExpectedWithNewMetamodel; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.SkipForDialects; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; +import org.junit.Test; /** * @author Gavin King @@ -80,7 +80,9 @@ public class CompositeUserTypeTest extends BaseCoreFunctionalTestCase { } @Test - @SkipForDialect( value = {SybaseASE15Dialect.class, DB2Dialect.class}, jiraKey = "HHH-6788,HHH-6867") + @SkipForDialects( { + @SkipForDialect ( value = { SybaseASE15Dialect.class }, jiraKey = "HHH-6788" ), + @SkipForDialect ( value = { DB2Dialect.class }, jiraKey = "HHH-6867" ) } ) public void testCustomColumnReadAndWrite() { Session s = openSession(); org.hibernate.Transaction t = s.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Category.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Category.java new file mode 100644 index 0000000000..464a718ef2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Category.java @@ -0,0 +1,54 @@ +package org.hibernate.test.dialect.functional; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +public class Category implements Serializable { + @Id + public Integer id; + + public String name; + + @OneToMany(mappedBy = "category") + public List products; + + public Category() { + } + + public Category(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof Category ) ) return false; + + Category category = (Category) o; + + if ( id != null ? !id.equals( category.id ) : category.id != null ) return false; + if ( name != null ? !name.equals( category.name ) : category.name != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( name != null ? name.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Category(id = " + id + ", name = " + name + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Contact.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Contact.java new file mode 100644 index 0000000000..d8f07ee0ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Contact.java @@ -0,0 +1,73 @@ +package org.hibernate.test.dialect.functional; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Table(name = "contacts") +public class Contact implements Serializable { + @Id + @Column(name = "id") + public Long id; + + @Column(name = "type") + public String type; + + @Column(name = "firstname") + public String firstName; + + @Column(name = "lastname") + public String lastName; + + @ManyToOne + @JoinColumn(name = "folder_id") + public Folder folder; + + public Contact() { + } + + public Contact(Long id, String firstName, String lastName, String type, Folder folder) { + this.firstName = firstName; + this.folder = folder; + this.id = id; + this.lastName = lastName; + this.type = type; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof Contact ) ) return false; + + Contact contact = (Contact) o; + + if ( id != null ? !id.equals( contact.id ) : contact.id != null ) return false; + if ( firstName != null ? !firstName.equals( contact.firstName ) : contact.firstName != null ) return false; + if ( lastName != null ? !lastName.equals( contact.lastName ) : contact.lastName != null ) return false; + if ( type != null ? !type.equals( contact.type ) : contact.type != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( type != null ? type.hashCode() : 0 ); + result = 31 * result + ( firstName != null ? firstName.hashCode() : 0 ); + result = 31 * result + ( lastName != null ? lastName.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Contact(id = " + id + ", type = " + type + ", firstName = " + firstName + ", lastName = " + lastName + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Folder.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Folder.java new file mode 100644 index 0000000000..49450adebe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Folder.java @@ -0,0 +1,58 @@ +package org.hibernate.test.dialect.functional; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Formula; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Table(name = "folders") +public class Folder implements Serializable { + @Id + public Long id; + + public String name; + + @Formula("( SELECT CASE WHEN c.type = 'owner' THEN c.firstname + ' ' + c.lastname END FROM contacts c where c.folder_id = id )") + public String owner; + + public Folder() { + } + + public Folder(Long id, String name) { + this.id = id; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof Folder ) ) return false; + + Folder folder = (Folder) o; + + if ( id != null ? !id.equals( folder.id ) : folder.id != null ) return false; + if ( name != null ? !name.equals( folder.name ) : folder.name != null ) return false; + if ( owner != null ? !owner.equals( folder.owner ) : folder.owner != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( name != null ? name.hashCode() : 0 ); + result = 31 * result + ( owner != null ? owner.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Folder(id = " + id + ", name = " + name + ", owner = " + owner + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Product2.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Product2.java index 3276a4280a..1059a8e3f7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Product2.java +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/Product2.java @@ -31,6 +31,7 @@ import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.ManyToOne; /** * @author Guenther Demetz @@ -43,6 +44,9 @@ public class Product2 implements Serializable { @Column(name = "description", nullable = false) public String description; + @ManyToOne + public Category category; + public Product2() { } @@ -51,6 +55,12 @@ public class Product2 implements Serializable { this.description = description; } + public Product2(Integer id, String description, Category category) { + this.category = category; + this.description = description; + this.id = id; + } + @Override public boolean equals(Object o) { if ( this == o ) return true; diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectTest.java index e241828236..3f8640ab7b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectTest.java @@ -26,9 +26,14 @@ */ package org.hibernate.test.dialect.functional; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.Arrays; import java.util.List; @@ -38,17 +43,16 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.criterion.Order; +import org.hibernate.criterion.Projections; import org.hibernate.dialect.SQLServer2005Dialect; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.exception.LockTimeoutException; import org.hibernate.jdbc.ReturningWork; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - /** * used driver hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver * @@ -60,13 +64,14 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase { @TestForIssue(jiraKey = "HHH-7198") public void testMaxResultsSqlServerWithCaseSensitiveCollation() throws Exception { - Session s = openSession(); + final Session s = openSession(); s.beginTransaction(); String defaultCollationName = s.doReturningWork( new ReturningWork() { @Override public String execute(Connection connection) throws SQLException { String databaseName = connection.getCatalog(); - ResultSet rs = connection.createStatement().executeQuery( "SELECT collation_name FROM sys.databases WHERE name = '"+databaseName+ "';" ); + Statement st = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); + ResultSet rs = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( st, "SELECT collation_name FROM sys.databases WHERE name = '"+databaseName+ "';" ); while(rs.next()){ return rs.getString( "collation_name" ); } @@ -77,40 +82,44 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase { s.getTransaction().commit(); s.close(); - s = openSession(); - String databaseName = s.doReturningWork( new ReturningWork() { + Session s2 = openSession(); + String databaseName = s2.doReturningWork( new ReturningWork() { @Override public String execute(Connection connection) throws SQLException { return connection.getCatalog(); } } ); - s.createSQLQuery( "ALTER DATABASE " + databaseName + " set single_user with rollback immediate" ) + s2.createSQLQuery( "ALTER DATABASE " + databaseName + " set single_user with rollback immediate" ) .executeUpdate(); - s.createSQLQuery( "ALTER DATABASE " + databaseName + " COLLATE Latin1_General_CS_AS" ).executeUpdate(); - s.createSQLQuery( "ALTER DATABASE " + databaseName + " set multi_user" ).executeUpdate(); + s2.createSQLQuery( "ALTER DATABASE " + databaseName + " COLLATE Latin1_General_CS_AS" ).executeUpdate(); + s2.createSQLQuery( "ALTER DATABASE " + databaseName + " set multi_user" ).executeUpdate(); - Transaction tx = s.beginTransaction(); + Transaction tx = s2.beginTransaction(); for ( int i = 1; i <= 20; i++ ) { - s.persist( new Product2( i, "Kit" + i ) ); + s2.persist( new Product2( i, "Kit" + i ) ); } - s.flush(); - s.clear(); + s2.flush(); + s2.clear(); - List list = s.createQuery( "from Product2 where description like 'Kit%'" ) + List list = s2.createQuery( "from Product2 where description like 'Kit%'" ) .setFirstResult( 2 ) .setMaxResults( 2 ) .list(); assertEquals( 2, list.size() ); tx.rollback(); - s.close(); + s2.close(); - s = openSession(); - s.createSQLQuery( "ALTER DATABASE " + databaseName + " set single_user with rollback immediate" ) + s2 = openSession(); + s2.createSQLQuery( "ALTER DATABASE " + databaseName + " set single_user with rollback immediate" ) .executeUpdate(); - s.createSQLQuery( "ALTER DATABASE " + databaseName + " COLLATE " + defaultCollationName ).executeUpdate(); - s.createSQLQuery( "ALTER DATABASE " + databaseName + " set multi_user" ).executeUpdate(); - s.close(); + s2.createSQLQuery( "ALTER DATABASE " + databaseName + " COLLATE " + defaultCollationName ).executeUpdate(); + s2.createSQLQuery( "ALTER DATABASE " + databaseName + " set multi_user" ).executeUpdate(); + s2.close(); + } + + private void doWork(Session s) { + } @Test @@ -214,6 +223,102 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase { session.close(); } + @Test + @TestForIssue(jiraKey = "HHH-6627") + public void testPaginationWithAggregation() { + Session session = openSession(); + Transaction tx = session.beginTransaction(); + + // populating test data + Category category1 = new Category( 1, "Category1" ); + Category category2 = new Category( 2, "Category2" ); + Category category3 = new Category( 3, "Category3" ); + session.persist( category1 ); + session.persist( category2 ); + session.persist( category3 ); + session.flush(); + session.persist( new Product2( 1, "Kit1", category1 ) ); + session.persist( new Product2( 2, "Kit2", category1 ) ); + session.persist( new Product2( 3, "Kit3", category1 ) ); + session.persist( new Product2( 4, "Kit4", category2 ) ); + session.persist( new Product2( 5, "Kit5", category2 ) ); + session.persist( new Product2( 6, "Kit6", category3 ) ); + session.flush(); + session.clear(); + + // count number of products in each category + List result = session.createCriteria( Category.class, "c" ).createAlias( "products", "p" ) + .setProjection( + Projections.projectionList() + .add( Projections.groupProperty( "c.id" ) ) + .add( Projections.countDistinct( "p.id" ) ) + ) + .addOrder( Order.asc( "c.id" ) ) + .setFirstResult( 1 ).setMaxResults( 3 ).list(); + + assertEquals( 2, result.size() ); + assertArrayEquals( new Object[] { 2, 2L }, result.get( 0 ) ); // two products of second category + assertArrayEquals( new Object[] { 3, 1L }, result.get( 1 ) ); // one products of third category + + tx.rollback(); + session.close(); + } + + @Test + @TestForIssue(jiraKey = "HHH-7752") + public void testPaginationWithFormulaSubquery() { + Session session = openSession(); + Transaction tx = session.beginTransaction(); + + // populating test data + Folder folder1 = new Folder( 1L, "Folder1" ); + Folder folder2 = new Folder( 2L, "Folder2" ); + Folder folder3 = new Folder( 3L, "Folder3" ); + session.persist( folder1 ); + session.persist( folder2 ); + session.persist( folder3 ); + session.flush(); + session.persist( new Contact( 1L, "Lukasz", "Antoniak", "owner", folder1 ) ); + session.persist( new Contact( 2L, "Kinga", "Mroz", "co-owner", folder2 ) ); + session.flush(); + session.clear(); + session.refresh( folder1 ); + session.refresh( folder2 ); + session.clear(); + + List folderCount = session.createQuery( "select count(distinct f) from Folder f" ).setMaxResults( 1 ).list(); + assertEquals( Arrays.asList( 3L ), folderCount ); + + List distinctFolders = session.createQuery( "select distinct f from Folder f order by f.id desc" ) + .setFirstResult( 1 ).setMaxResults( 2 ).list(); + assertEquals( Arrays.asList( folder2, folder1 ), distinctFolders ); + + tx.rollback(); + session.close(); + } + + @Test + @TestForIssue(jiraKey = "HHH-7781") + public void testPaginationWithCastOperator() { + Session session = openSession(); + Transaction tx = session.beginTransaction(); + + for ( int i = 40; i < 50; i++ ) { + session.persist( new Product2( i, "Kit" + i ) ); + } + session.flush(); + session.clear(); + + List list = session.createQuery( "select p.id, cast(p.id as string) as string_id from Product2 p order by p.id" ) + .setFirstResult( 1 ).setMaxResults( 2 ).list(); + assertEquals( 2, list.size() ); + assertArrayEquals( new Object[] { 41, "41" }, list.get( 0 ) ); + assertArrayEquals( new Object[] { 42, "42" }, list.get( 1 ) ); + + tx.rollback(); + session.close(); + } + @Test @TestForIssue(jiraKey = "HHH-3961") public void testLockNowaitSqlServer() throws Exception { @@ -274,14 +379,12 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase { s.getTransaction().begin(); s.delete( kit ); s.getTransaction().commit(); - - } @Override protected java.lang.Class[] getAnnotatedClasses() { return new java.lang.Class[] { - Product2.class + Product2.class, Category.class, Folder.class, Contact.class }; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java index 40af202bf4..7ca70682c9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java @@ -45,6 +45,7 @@ import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.dialect.Cache71Dialect; import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.test.legacy.Blobber; import org.hibernate.test.legacy.Broken; @@ -648,15 +649,15 @@ public class SQLFunctionsInterSystemsTest extends BaseCoreFunctionalTestCase { java.sql.Timestamp testvalue3 = new java.sql.Timestamp(cal3.getTimeInMillis()); testvalue3.setNanos(0); - Session s = openSession(); + final Session s = openSession(); s.beginTransaction(); try { s.doWork( new Work() { @Override public void execute(Connection connection) throws SQLException { - Statement stmt = connection.createStatement(); - stmt.executeUpdate( "DROP FUNCTION spLock FROM TestInterSystemsFunctionsClass" ); + Statement stmt = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); + ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( stmt, "DROP FUNCTION spLock FROM TestInterSystemsFunctionsClass" ); } } ); @@ -672,7 +673,7 @@ public class SQLFunctionsInterSystemsTest extends BaseCoreFunctionalTestCase { new Work() { @Override public void execute(Connection connection) throws SQLException { - Statement stmt = connection.createStatement(); + Statement stmt = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); String create_function = "CREATE FUNCTION SQLUser.TestInterSystemsFunctionsClass_spLock\n" + " ( INOUT pHandle %SQLProcContext, \n" + " ROWID INTEGER \n" + @@ -684,7 +685,7 @@ public class SQLFunctionsInterSystemsTest extends BaseCoreFunctionalTestCase { " {\n" + " q 0\n" + " }"; - stmt.executeUpdate(create_function); + ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( stmt, create_function ); } } ); @@ -700,70 +701,70 @@ public class SQLFunctionsInterSystemsTest extends BaseCoreFunctionalTestCase { s.getTransaction().commit(); s.close(); - s = openSession(); - s.beginTransaction(); - TestInterSystemsFunctionsClass test = (TestInterSystemsFunctionsClass) s.get(TestInterSystemsFunctionsClass.class, Long.valueOf(10)); + Session s2 = openSession(); + s2.beginTransaction(); + TestInterSystemsFunctionsClass test = (TestInterSystemsFunctionsClass) s2.get(TestInterSystemsFunctionsClass.class, Long.valueOf(10)); assertTrue( test.getDate1().equals(testvalue)); - test = (TestInterSystemsFunctionsClass) s.get(TestInterSystemsFunctionsClass.class, Long.valueOf(10), LockMode.UPGRADE); + test = (TestInterSystemsFunctionsClass) s2.get(TestInterSystemsFunctionsClass.class, Long.valueOf(10), LockMode.UPGRADE); assertTrue( test.getDate1().equals(testvalue)); - Date value = (Date) s.createQuery( "select nvl(o.date,o.dateText) from TestInterSystemsFunctionsClass as o" ) + Date value = (Date) s2.createQuery( "select nvl(o.date,o.dateText) from TestInterSystemsFunctionsClass as o" ) .list() .get(0); assertTrue( value.equals(testvalue)); - Object nv = s.createQuery( "select nullif(o.dateText,o.dateText) from TestInterSystemsFunctionsClass as o" ) + Object nv = s2.createQuery( "select nullif(o.dateText,o.dateText) from TestInterSystemsFunctionsClass as o" ) .list() .get(0); assertTrue( nv == null); - String dateText = (String) s.createQuery( + String dateText = (String) s2.createQuery( "select nvl(o.dateText,o.date) from TestInterSystemsFunctionsClass as o" ).list() .get(0); assertTrue( dateText.equals("1977-07-03")); - value = (Date) s.createQuery( "select ifnull(o.date,o.date1) from TestInterSystemsFunctionsClass as o" ) + value = (Date) s2.createQuery( "select ifnull(o.date,o.date1) from TestInterSystemsFunctionsClass as o" ) .list() .get(0); assertTrue( value.equals(testvalue)); - value = (Date) s.createQuery( "select ifnull(o.date3,o.date,o.date1) from TestInterSystemsFunctionsClass as o" ) + value = (Date) s2.createQuery( "select ifnull(o.date3,o.date,o.date1) from TestInterSystemsFunctionsClass as o" ) .list() .get(0); assertTrue( value.equals(testvalue)); - Integer pos = (Integer) s.createQuery( + Integer pos = (Integer) s2.createQuery( "select position('07', o.dateText) from TestInterSystemsFunctionsClass as o" ).list() .get(0); assertTrue(pos.intValue() == 6); - String st = (String) s.createQuery( "select convert(o.date1, SQL_TIME) from TestInterSystemsFunctionsClass as o" ) + String st = (String) s2.createQuery( "select convert(o.date1, SQL_TIME) from TestInterSystemsFunctionsClass as o" ) .list() .get(0); assertTrue( st.equals("00:00:00")); - java.sql.Time tm = (java.sql.Time) s.createQuery( + java.sql.Time tm = (java.sql.Time) s2.createQuery( "select cast(o.date1, time) from TestInterSystemsFunctionsClass as o" ).list() .get(0); assertTrue( tm.toString().equals("00:00:00")); - Double diff = (Double) s.createQuery( + Double diff = (Double) s2.createQuery( "select timestampdiff(SQL_TSI_FRAC_SECOND, o.date3, o.date1) from TestInterSystemsFunctionsClass as o" ).list() .get(0); assertTrue(diff.doubleValue() != 0.0); - diff = (Double) s.createQuery( + diff = (Double) s2.createQuery( "select timestampdiff(SQL_TSI_MONTH, o.date3, o.date1) from TestInterSystemsFunctionsClass as o" ).list() .get(0); assertTrue(diff.doubleValue() == 16.0); - diff = (Double) s.createQuery( + diff = (Double) s2.createQuery( "select timestampdiff(SQL_TSI_WEEK, o.date3, o.date1) from TestInterSystemsFunctionsClass as o" ).list() .get(0); assertTrue(diff.doubleValue() >= 16*4); - diff = (Double) s.createQuery( + diff = (Double) s2.createQuery( "select timestampdiff(SQL_TSI_YEAR, o.date3, o.date1) from TestInterSystemsFunctionsClass as o" ).list() .get(0); assertTrue(diff.doubleValue() == 1.0); - s.getTransaction().commit(); - s.close(); + s2.getTransaction().commit(); + s2.close(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/AggregatedCollectionEventListener.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/AggregatedCollectionEventListener.java new file mode 100644 index 0000000000..c3938003ae --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/AggregatedCollectionEventListener.java @@ -0,0 +1,200 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Configuration; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.AbstractCollectionEvent; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.InitializeCollectionEvent; +import org.hibernate.event.spi.InitializeCollectionEventListener; +import org.hibernate.event.spi.PostCollectionRecreateEvent; +import org.hibernate.event.spi.PostCollectionRecreateEventListener; +import org.hibernate.event.spi.PostCollectionRemoveEvent; +import org.hibernate.event.spi.PostCollectionRemoveEventListener; +import org.hibernate.event.spi.PostCollectionUpdateEvent; +import org.hibernate.event.spi.PostCollectionUpdateEventListener; +import org.hibernate.event.spi.PreCollectionRecreateEvent; +import org.hibernate.event.spi.PreCollectionRecreateEventListener; +import org.hibernate.event.spi.PreCollectionRemoveEvent; +import org.hibernate.event.spi.PreCollectionRemoveEventListener; +import org.hibernate.event.spi.PreCollectionUpdateEvent; +import org.hibernate.event.spi.PreCollectionUpdateEventListener; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.metamodel.spi.MetadataImplementor; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; + +/** + * @author Steve Ebersole + * @author Gail Badner + */ +public class AggregatedCollectionEventListener + implements InitializeCollectionEventListener, + PreCollectionRecreateEventListener, + PostCollectionRecreateEventListener, + PreCollectionRemoveEventListener, + PostCollectionRemoveEventListener, + PreCollectionUpdateEventListener, + PostCollectionUpdateEventListener { + + private static final Logger log = Logger.getLogger( AggregatedCollectionEventListener.class ); + + private final List eventEntryList = new ArrayList(); + + public void reset() { + eventEntryList.clear(); + } + + public List getEventEntryList() { + return eventEntryList; + } + + @Override + public void onInitializeCollection(InitializeCollectionEvent event) throws HibernateException { + addEvent( event ); + } + + protected void addEvent(AbstractCollectionEvent event) { + log.debugf( "Added collection event : %s", event ); + eventEntryList.add( new EventEntry( event ) ); + } + + + // recreate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public void onPreRecreateCollection(PreCollectionRecreateEvent event) { + addEvent( event ); + } + + @Override + public void onPostRecreateCollection(PostCollectionRecreateEvent event) { + addEvent( event ); + } + + + // remove ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public void onPreRemoveCollection(PreCollectionRemoveEvent event) { + addEvent( event ); + } + + @Override + public void onPostRemoveCollection(PostCollectionRemoveEvent event) { + addEvent( event ); + } + + + // update ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public void onPreUpdateCollection(PreCollectionUpdateEvent event) { + addEvent( event ); + } + + @Override + public void onPostUpdateCollection(PostCollectionUpdateEvent event) { + addEvent( event ); + } + + public static class EventEntry { + private final AbstractCollectionEvent event; + private final Serializable snapshotAtTimeOfEventHandling; + + public EventEntry(AbstractCollectionEvent event) { + this.event = event; + // make a copy of the collection? + this.snapshotAtTimeOfEventHandling = event.getSession() + .getPersistenceContext() + .getCollectionEntry( event.getCollection() ) + .getSnapshot(); + } + + public AbstractCollectionEvent getEvent() { + return event; + } + + public Serializable getSnapshotAtTimeOfEventHandling() { + return snapshotAtTimeOfEventHandling; + } + } + + public static class IntegratorImpl implements Integrator { + private AggregatedCollectionEventListener listener; + + public AggregatedCollectionEventListener getListener() { + if ( listener == null ) { + throw new HibernateException( "Integrator not yet processed" ); + } + return listener; + } + + @Override + public void integrate( + Configuration configuration, + SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + integrate( serviceRegistry ); + } + + protected void integrate(SessionFactoryServiceRegistry serviceRegistry) { + if ( listener != null ) { + log.warn( "integrate called second time on testing collection listener Integrator (could be result of rebuilding SF on test failure)" ); + } + listener = new AggregatedCollectionEventListener(); + + final EventListenerRegistry listenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); + listenerRegistry.appendListeners( EventType.INIT_COLLECTION, listener ); + listenerRegistry.appendListeners( EventType.PRE_COLLECTION_RECREATE, listener ); + listenerRegistry.appendListeners( EventType.POST_COLLECTION_RECREATE, listener ); + listenerRegistry.appendListeners( EventType.PRE_COLLECTION_REMOVE, listener ); + listenerRegistry.appendListeners( EventType.POST_COLLECTION_REMOVE, listener ); + listenerRegistry.appendListeners( EventType.PRE_COLLECTION_UPDATE, listener ); + listenerRegistry.appendListeners( EventType.POST_COLLECTION_UPDATE, listener ); + } + + + @Override + public void integrate( + MetadataImplementor metadata, + SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + integrate( serviceRegistry ); + } + + @Override + public void disintegrate( + SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Alias.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Alias.java new file mode 100644 index 0000000000..f5b864727c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Alias.java @@ -0,0 +1,86 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Steve Ebersole + */ +@Entity +public class Alias implements Identifiable { + private Integer id; + private String alias; + private List characters = new ArrayList(); + + public Alias() { + } + + public Alias(Integer id, String alias) { + this.id = id; + this.alias = alias; + } + + @Id + @Override + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + @ManyToMany( cascade = CascadeType.ALL ) + @JoinTable( name = "CHARACTER_ALIAS", indexes = @Index( columnList = "characters_id")) +// @JoinTable( +// name = "CHARACTER_ALIAS", +// joinColumns = @JoinColumn(name="ALIAS_ID", referencedColumnName="ID"), +// inverseJoinColumns = @JoinColumn(name="CHARACTER_ID", referencedColumnName="ID") +// ) + public List getCharacters() { + return characters; + } + + public void setCharacters(List characters) { + this.characters = characters; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/BadMergeHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/BadMergeHandlingTest.java new file mode 100644 index 0000000000..103ad483cf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/BadMergeHandlingTest.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import java.util.List; + +import org.hibernate.Session; + +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-7928" ) +public class BadMergeHandlingTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Character.class, Alias.class }; + } + + @Test + @TestForIssue( jiraKey = "HHH-7928" ) + public void testMergeAndHold() { + Session s = openSession(); + s.beginTransaction(); + + Character paul = new Character( 1, "Paul Atreides" ); + s.persist( paul ); + Character paulo = new Character( 2, "Paulo Atreides" ); + s.persist( paulo ); + + Alias alias1 = new Alias( 1, "Paul Muad'Dib" ); + s.persist( alias1 ); + + Alias alias2 = new Alias( 2, "Usul" ); + s.persist( alias2 ); + + Alias alias3 = new Alias( 3, "The Preacher" ); + s.persist( alias3 ); + + s.getTransaction().commit(); + s.close(); + + // set up relationships + s = openSession(); + s.beginTransaction(); + + // customer 1 + alias1.getCharacters().add( paul ); + s.merge( alias1 ); + alias2.getCharacters().add( paul ); + s.merge( alias2 ); + alias3.getCharacters().add( paul ); + s.merge( alias3 ); + + s.flush(); + + // customer 2 + alias1.getCharacters().add( paulo ); + s.merge( alias1 ); + alias2.getCharacters().add( paulo ); + s.merge( alias2 ); + alias3.getCharacters().add( paulo ); + s.merge( alias3 ); + s.flush(); + + s.getTransaction().commit(); + s.close(); + + // now try to read them back (I guess) + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "select c from Character c join c.aliases a where a.alias = :aParam" ) + .setParameter( "aParam", "Usul" ) + .list(); + assertEquals( 2, results.size() ); + s.getTransaction().commit(); + s.close(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Character.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Character.java new file mode 100644 index 0000000000..d93ae072f2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Character.java @@ -0,0 +1,85 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +// "Character" is reserved in MySQL +@Table( name = "CharacterTable" ) +public class Character implements Identifiable { + private Integer id; + private String name; + private List aliases = new ArrayList(); + + public Character() { + } + + public Character(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + @Override + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToMany( cascade= CascadeType.ALL, mappedBy="characters" ) + public List getAliases() { + return aliases; + } + + public void setAliases(List aliases) { + this.aliases = aliases; + } + + public void associateAlias(Alias alias) { + alias.getCharacters().add( this ); + getAliases().add( alias ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/DetachedMultipleCollectionChangeTest.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/DetachedMultipleCollectionChangeTest.java index ec2e4ea75d..6ad8a41a3a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/DetachedMultipleCollectionChangeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/DetachedMultipleCollectionChangeTest.java @@ -238,7 +238,7 @@ public class DetachedMultipleCollectionChangeTest extends BaseCoreFunctionalTest org.hibernate.test.event.collection.Entity ownerExpected, List expectedCollectionEntrySnapshot, int index) { - AbstractCollectionEvent event = (AbstractCollectionEvent) listeners + AbstractCollectionEvent event = listeners .getEvents().get(index); assertSame(listenerExpected, listeners.getListenersCalled().get(index)); diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Identifiable.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Identifiable.java new file mode 100644 index 0000000000..9db49a911e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Identifiable.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +/** + * @author Steve Ebersole + */ +public interface Identifiable { + public Integer getId(); +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MergeCollectionEventTest.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MergeCollectionEventTest.java new file mode 100644 index 0000000000..758f5839cd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MergeCollectionEventTest.java @@ -0,0 +1,251 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.event.spi.AbstractCollectionEvent; +import org.hibernate.event.spi.PostCollectionRecreateEvent; +import org.hibernate.event.spi.PostCollectionUpdateEvent; +import org.hibernate.event.spi.PreCollectionRecreateEvent; +import org.hibernate.event.spi.PreCollectionRemoveEvent; +import org.hibernate.event.spi.PreCollectionUpdateEvent; + +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-7928" ) +public class MergeCollectionEventTest extends BaseCoreFunctionalTestCase { + + private AggregatedCollectionEventListener.IntegratorImpl collectionListenerIntegrator = + new AggregatedCollectionEventListener.IntegratorImpl(); + + @Before + public void resetListener() { + collectionListenerIntegrator.getListener().reset(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Character.class, Alias.class }; + } + + @Override + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + super.prepareBootstrapRegistryBuilder( builder ); + builder.with( collectionListenerIntegrator ); + } + + @Override + protected void cleanupTestData() throws Exception { + Session s = openSession(); + s.beginTransaction(); + List aliases = s.createQuery( "from Alias" ).list(); + for ( Alias alias : aliases ) { + for ( Character character : alias.getCharacters() ) { + character.getAliases().clear(); + } + alias.getCharacters().clear(); + } + s.flush(); + s.createQuery( "delete Alias" ).executeUpdate(); + s.createQuery( "delete Character" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testCollectionEventHandlingOnMerge() { + final AggregatedCollectionEventListener listener = collectionListenerIntegrator.getListener(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // This first bit really is just preparing the entities. There is generally no collection + // events of real interest during this part + + Session s = openSession(); + s.beginTransaction(); + Character paul = new Character( 1, "Paul Atreides" ); + s.save( paul ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, paul, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, paul, Collections.EMPTY_LIST ); + + listener.reset(); + + s = openSession(); + s.beginTransaction(); + Character paulo = new Character( 2, "Paulo Atreides" ); + s.save( paulo ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, paulo, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, paulo, Collections.EMPTY_LIST ); + + listener.reset(); + + s = openSession(); + s.beginTransaction(); + Alias alias1 = new Alias( 1, "Paul Muad'Dib" ); + s.save( alias1 ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, alias1, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, alias1, Collections.EMPTY_LIST ); + + listener.reset(); + + s = openSession(); + s.beginTransaction(); + Alias alias2 = new Alias( 2, "Usul" ); + s.save( alias2 ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, alias2, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, alias2, Collections.EMPTY_LIST ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // at this point we can start setting up the associations and checking collection events + // of "real interest" + + listener.reset(); + + paul.associateAlias( alias1 ); + paul.associateAlias( alias2 ); + + paulo.associateAlias( alias1 ); + paulo.associateAlias( alias2 ); + + s = openSession(); + s.beginTransaction(); + s.merge( alias1 ); + + assertEquals( 0, listener.getEventEntryList().size() ); + + // this is where HHH-7928 (problem with HHH-6361 fix) shows up... + s.flush(); + + assertEquals( 8, listener.getEventEntryList().size() ); // 4 collections x 2 events per + checkListener( 0, PreCollectionUpdateEvent.class, alias1, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionUpdateEvent.class, alias1, alias1.getCharacters() ); + checkListener( 2, PreCollectionUpdateEvent.class, paul, Collections.EMPTY_LIST ); + checkListener( 3, PostCollectionUpdateEvent.class, paul, paul.getAliases() ); + checkListener( 4, PreCollectionUpdateEvent.class, alias2, Collections.EMPTY_LIST ); + checkListener( 5, PostCollectionUpdateEvent.class, alias2, alias2.getCharacters() ); + checkListener( 6, PreCollectionUpdateEvent.class, paulo, Collections.EMPTY_LIST ); + checkListener( 7, PostCollectionUpdateEvent.class, paulo, paul.getAliases() ); + + List alias1CharactersSnapshot = copy( alias1.getCharacters() ); + List alias2CharactersSnapshot = copy( alias2.getCharacters() ); + + listener.reset(); + + s.merge( alias2 ); + + assertEquals( 0, listener.getEventEntryList().size() ); + + s.flush(); + + assertEquals( 8, listener.getEventEntryList().size() ); // 4 collections x 2 events per + checkListener( 0, PreCollectionUpdateEvent.class, alias1, alias1CharactersSnapshot ); + checkListener( 1, PostCollectionUpdateEvent.class, alias1, alias1CharactersSnapshot ); +// checkListener( 2, PreCollectionUpdateEvent.class, paul, Collections.EMPTY_LIST ); +// checkListener( 3, PostCollectionUpdateEvent.class, paul, paul.getAliases() ); + checkListener( 4, PreCollectionUpdateEvent.class, alias2, alias2CharactersSnapshot ); + checkListener( 5, PostCollectionUpdateEvent.class, alias2, alias2.getCharacters() ); +// checkListener( 6, PreCollectionUpdateEvent.class, paulo, Collections.EMPTY_LIST ); +// checkListener( 7, PostCollectionUpdateEvent.class, paulo, paul.getAliases() ); + + s.getTransaction().commit(); + s.close(); + +// +// checkListener(listeners, listeners.getInitializeCollectionListener(), +// mce, null, eventCount++); +// checkListener(listeners, listeners.getPreCollectionUpdateListener(), +// mce, oldRefentities1, eventCount++); +// checkListener(listeners, listeners.getPostCollectionUpdateListener(), +// mce, mce.getRefEntities1(), eventCount++); + + } + + + protected void checkListener( + int eventIndex, + Class expectedEventType, + Identifiable expectedOwner, + List expectedCollectionEntrySnapshot) { + final AggregatedCollectionEventListener.EventEntry eventEntry + = collectionListenerIntegrator.getListener().getEventEntryList().get( eventIndex ); + final AbstractCollectionEvent event = eventEntry.getEvent(); + + assertTyping( expectedEventType, event ); + +// because of the merge graphs, the instances are likely different. just base check on type and id +// assertEquals( expectedOwner, event.getAffectedOwnerOrNull() ); + assertEquals( expectedOwner.getClass().getName(), event.getAffectedOwnerEntityName() ); + assertEquals( expectedOwner.getId(), event.getAffectedOwnerIdOrNull() ); + + if ( event instanceof PreCollectionUpdateEvent + || event instanceof PreCollectionRemoveEvent + || event instanceof PostCollectionRecreateEvent ) { + List snapshot = (List) eventEntry.getSnapshotAtTimeOfEventHandling(); + assertEquals( expectedCollectionEntrySnapshot.size(), snapshot.size() ); + for ( int i = 0; i < expectedCollectionEntrySnapshot.size(); i++ ) { + Identifiable expected = expectedCollectionEntrySnapshot.get( i ); + Identifiable found = snapshot.get( i ); + assertEquals( expected.getClass().getName(), found.getClass().getName() ); + assertEquals( expected.getId(), found.getId() ); + } + } + } + + private List copy(List source) { + ArrayList copy = new ArrayList( source.size() ); + copy.addAll( source ); + return copy; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java index 333da328dc..6d10287564 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java @@ -203,10 +203,10 @@ public class MultipleCollectionListeners { public void addEvent(AbstractCollectionEvent event, Listener listener) { + CollectionEntry collectionEntry = event.getSession() .getPersistenceContext() .getCollectionEntry(event.getCollection()); - Serializable snapshot = collectionEntry.getSnapshot(); log.debug("add Event: " + event.getClass() + "; listener = " diff --git a/hibernate-core/src/test/java/org/hibernate/test/eviction/EvictionTest.java b/hibernate-core/src/test/java/org/hibernate/test/eviction/EvictionTest.java new file mode 100644 index 0000000000..db5d530ed5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/eviction/EvictionTest.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.eviction; + +import org.hibernate.Session; + +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Steve Ebersole + */ +public class EvictionTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { IsolatedEvictableEntity.class }; + } + + @Test + @TestForIssue( jiraKey = "HHH-7912" ) + public void testNormalUsage() { + Session session = openSession(); + session.beginTransaction(); + session.save( new IsolatedEvictableEntity( 1 ) ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + IsolatedEvictableEntity entity = (IsolatedEvictableEntity) session.get( IsolatedEvictableEntity.class, 1 ); + assertTrue( session.contains( entity ) ); + session.evict( entity ); + assertFalse( session.contains( entity ) ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + session.delete( entity ); + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-7912" ) + public void testEvictingNull() { + Session session = openSession(); + session.beginTransaction(); + try { + session.evict( null ); + fail( "Expecting evict(null) to throw NPE" ); + } + catch (NullPointerException expected) { + } + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-7912" ) + public void testEvictingTransientEntity() { + Session session = openSession(); + session.beginTransaction(); + session.evict( new IsolatedEvictableEntity( 1 ) ); + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-7912" ) + public void testEvictingDetachedEntity() { + Session session = openSession(); + session.beginTransaction(); + session.save( new IsolatedEvictableEntity( 1 ) ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + IsolatedEvictableEntity entity = (IsolatedEvictableEntity) session.get( IsolatedEvictableEntity.class, 1 ); + assertTrue( session.contains( entity ) ); + // detach the entity + session.evict( entity ); + assertFalse( session.contains( entity ) ); + // evict it again the entity + session.evict( entity ); + assertFalse( session.contains( entity ) ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + session.delete( entity ); + session.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-7912" ) + public void testEvictingNonEntity() { + Session session = openSession(); + session.beginTransaction(); + try { + session.evict( new EvictionTest() ); + fail( "Expecting evict(non-entity) to throw IAE" ); + } + catch (IllegalArgumentException expected) { + } + session.getTransaction().commit(); + session.close(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/eviction/IsolatedEvictableEntity.java b/hibernate-core/src/test/java/org/hibernate/test/eviction/IsolatedEvictableEntity.java new file mode 100644 index 0000000000..844a26f064 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/eviction/IsolatedEvictableEntity.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.eviction; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class IsolatedEvictableEntity { + @Id + private Integer id; + private String name; + + public IsolatedEvictableEntity() { + } + + public IsolatedEvictableEntity(Integer id) { + this.id = id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java b/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java index 83eb999a4d..228fb2d200 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import org.hibernate.Session; import org.hibernate.dialect.MySQLMyISAMDialect; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.SQLGrammarException; import org.hibernate.jdbc.Work; @@ -57,7 +58,7 @@ public class SQLExceptionConversionTest extends BaseCoreFunctionalTestCase { comment = "MySQL (MyISAM) does not support FK violation checking" ) public void testIntegrityViolation() throws Exception { - Session session = openSession(); + final Session session = openSession(); session.beginTransaction(); session.doWork( @@ -68,10 +69,10 @@ public class SQLExceptionConversionTest extends BaseCoreFunctionalTestCase { // result in a constraint violation PreparedStatement ps = null; try { - ps = connection.prepareStatement("INSERT INTO T_MEMBERSHIP (user_id, group_id) VALUES (?, ?)"); + ps = ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( "INSERT INTO T_MEMBERSHIP (user_id, group_id) VALUES (?, ?)" ); ps.setLong(1, 52134241); // Non-existent user_id ps.setLong(2, 5342); // Non-existent group_id - ps.executeUpdate(); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps ); fail("INSERT should have failed"); } @@ -81,7 +82,7 @@ public class SQLExceptionConversionTest extends BaseCoreFunctionalTestCase { finally { if ( ps != null ) { try { - ps.close(); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().release( ps ); } catch( Throwable ignore ) { // ignore... @@ -98,7 +99,7 @@ public class SQLExceptionConversionTest extends BaseCoreFunctionalTestCase { @Test public void testBadGrammar() throws Exception { - Session session = openSession(); + final Session session = openSession(); session.beginTransaction(); session.doWork( @@ -108,8 +109,8 @@ public class SQLExceptionConversionTest extends BaseCoreFunctionalTestCase { // prepare/execute a query against a non-existent table PreparedStatement ps = null; try { - ps = connection.prepareStatement("SELECT user_id, user_name FROM tbl_no_there"); - ps.executeQuery(); + ps = ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( "SELECT user_id, user_name FROM tbl_no_there" ); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); fail("SQL compilation should have failed"); } @@ -119,7 +120,7 @@ public class SQLExceptionConversionTest extends BaseCoreFunctionalTestCase { finally { if ( ps != null ) { try { - ps.close(); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().release( ps ); } catch( Throwable ignore ) { // ignore... diff --git a/hibernate-core/src/test/java/org/hibernate/cfg/beanvalidation/TypeSafeActivatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/flush/BookStore.java similarity index 50% rename from hibernate-core/src/test/java/org/hibernate/cfg/beanvalidation/TypeSafeActivatorTest.java rename to hibernate-core/src/test/java/org/hibernate/test/flush/BookStore.java index 13347adcbd..6b858eeadd 100644 --- a/hibernate-core/src/test/java/org/hibernate/cfg/beanvalidation/TypeSafeActivatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/flush/BookStore.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -21,32 +21,60 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.cfg.beanvalidation; +package org.hibernate.test.flush; -import javax.validation.Validation; +import java.util.HashSet; +import java.util.Set; -import org.junit.Test; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; -import org.hibernate.HibernateException; +import org.hibernate.annotations.GenericGenerator; /** - * @author Hardy Ferentschik + * @author Guillaume Smet */ +@Entity +public class BookStore { -public class TypeSafeActivatorTest { + private Long id; + private String name; + private Set books = new HashSet(); - @Test(expected = IllegalArgumentException.class) - public void testNullValidatorFactoryThrowsException() throws Exception { - TypeSafeActivator.assertObjectIsValidatorFactoryInstance( null ); + public BookStore() { } - @Test(expected = HibernateException.class) - public void testNonValidatorFactoryInstanceThrowsException() throws Exception { - TypeSafeActivator.assertObjectIsValidatorFactoryInstance( new Object() ); + public BookStore(String name) { + this.name = name; } - @Test - public void testValidValidatorFactoryInstance() throws Exception { - TypeSafeActivator.assertObjectIsValidatorFactoryInstance( Validation.buildDefaultValidatorFactory() ); + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name = "increment", strategy = "increment") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToMany + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/flush/TestClearBatchFetchQueueAfterFlush.java b/hibernate-core/src/test/java/org/hibernate/test/flush/TestClearBatchFetchQueueAfterFlush.java new file mode 100644 index 0000000000..047e9ffefd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/flush/TestClearBatchFetchQueueAfterFlush.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.flush; + +import java.util.Iterator; + +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * Thanks to Jan Hodac and Laurent Almeras for providing test cases for this + * issue. + * + * @author Guillaume Smet + */ +@TestForIssue(jiraKey = "HHH-7821") +public class TestClearBatchFetchQueueAfterFlush extends BaseCoreFunctionalTestCase { + + public void configure(Configuration cfg) { + cfg.setProperty( Environment.DEFAULT_BATCH_FETCH_SIZE, "10" ); + } + + @Test + public void testClearBatchFetchQueueAfterFlush() { + Session s = openSession(); + s.beginTransaction(); + + Author author1 = new Author( "David Lodge" ); + author1.getBooks().add( new Book( "A Man of Parts", author1 ) ); + author1.getBooks().add( new Book( "Thinks...", author1 ) ); + author1.getBooks().add( new Book( "Therapy", author1 ) ); + s.save( author1 ); + + Iterator bookIterator = author1.getBooks().iterator(); + + BookStore bookStore1 = new BookStore( "Passages" ); + bookStore1.getBooks().add( bookIterator.next() ); + s.save( bookStore1 ); + + BookStore bookStore2 = new BookStore( "Librairie du Tramway" ); + bookStore2.getBooks().add( bookIterator.next() ); + s.save( bookStore2 ); + + BookStore bookStore3 = new BookStore( "Le Bal des Ardents" ); + bookStore3.getBooks().add( bookIterator.next() ); + s.save( bookStore3 ); + + s.flush(); + s.getTransaction().commit(); + s.clear(); + + bookStore1 = (BookStore) s.load( BookStore.class, bookStore1.getId() ); + bookStore2 = (BookStore) s.load( BookStore.class, bookStore2.getId() ); + bookStore3 = (BookStore) s.load( BookStore.class, bookStore3.getId() ); + + s.beginTransaction(); + s.delete( bookStore2 ); + s.getTransaction().commit(); + + s.flush(); + + bookStore1.getBooks().size(); + bookStore3.getBooks().size(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Author.class, Book.class, Publisher.class, BookStore.class }; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index 8318149d98..f3c30cf157 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -55,6 +55,7 @@ import org.hibernate.TypeMismatchException; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.IngresDialect; import org.hibernate.dialect.MySQLDialect; @@ -83,6 +84,7 @@ import org.hibernate.test.cid.Product; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.FailureExpected; import org.hibernate.testing.FailureExpectedWithNewMetamodel; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; @@ -485,6 +487,33 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { s.close(); } + @Test + @TestForIssue( jiraKey = "HHH-2045" ) + @RequiresDialect( H2Dialect.class ) + public void testEmptyInList() { + Session session = openSession(); + session.beginTransaction(); + Human human = new Human(); + human.setName( new Name( "Lukasz", null, "Antoniak" ) ); + human.setNickName( "NONE" ); + session.save( human ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + List results = session.createQuery( "from Human h where h.nickName in ()" ).list(); + assertEquals( 0, results.size() ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + session.delete( human ); + session.getTransaction().commit(); + session.close(); + } + @Test public void testComponentNullnessChecks() { Session s = openSession(); @@ -527,6 +556,75 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { s.close(); } + @Test + @TestForIssue( jiraKey = "HHH-4150" ) + public void testSelectClauseCaseWithSum() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + + Human h1 = new Human(); + h1.setBodyWeight( 74.0f ); + h1.setDescription( "Me" ); + s.persist( h1 ); + + Human h2 = new Human(); + h2.setBodyWeight( 125.0f ); + h2.setDescription( "big persion #1" ); + s.persist( h2 ); + + Human h3 = new Human(); + h3.setBodyWeight( 110.0f ); + h3.setDescription( "big persion #2" ); + s.persist( h3 ); + + s.flush(); + + Number count = (Number) s.createQuery( "select sum(case when bodyWeight > 100 then 1 else 0 end) from Human" ).uniqueResult(); + assertEquals( 2, count.intValue() ); + count = (Number) s.createQuery( "select sum(case when bodyWeight > 100 then bodyWeight else 0 end) from Human" ).uniqueResult(); + assertEquals( h2.getBodyWeight() + h3.getBodyWeight(), count.floatValue(), 0.001 ); + + t.rollback(); + s.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-4150" ) + public void testSelectClauseCaseWithCountDistinct() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + + Human h1 = new Human(); + h1.setBodyWeight( 74.0f ); + h1.setDescription( "Me" ); + h1.setNickName( "Oney" ); + s.persist( h1 ); + + Human h2 = new Human(); + h2.setBodyWeight( 125.0f ); + h2.setDescription( "big persion" ); + h2.setNickName( "big #1" ); + s.persist( h2 ); + + Human h3 = new Human(); + h3.setBodyWeight( 110.0f ); + h3.setDescription( "big persion" ); + h3.setNickName( "big #2" ); + s.persist( h3 ); + + s.flush(); + + Number count = (Number) s.createQuery( "select count(distinct case when bodyWeight > 100 then description else null end) from Human" ).uniqueResult(); + assertEquals( 1, count.intValue() ); + count = (Number) s.createQuery( "select count(case when bodyWeight > 100 then description else null end) from Human" ).uniqueResult(); + assertEquals( 2, count.intValue() ); + count = (Number) s.createQuery( "select count(distinct case when bodyWeight > 100 then nickName else null end) from Human" ).uniqueResult(); + assertEquals( 2, count.intValue() ); + + t.rollback(); + s.close(); + } + @Test public void testInvalidCollectionDereferencesFail() { Session s = openSession(); @@ -2431,7 +2529,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { Zoo zooRead = ( Zoo ) results.get( 0 ); assertEquals( zoo, zooRead ); assertTrue( Hibernate.isInitialized( zooRead.getMammals() ) ); - Mammal mammalRead = ( Mammal ) ( ( Map ) zooRead.getMammals() ).get( "zebra" ); + Mammal mammalRead = ( Mammal ) zooRead.getMammals().get( "zebra" ); assertEquals( mammal, mammalRead ); session.delete( mammalRead ); session.delete( zooRead ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java index 49f4849d01..f2f7aea5a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java @@ -23,33 +23,35 @@ */ package org.hibernate.test.hql; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.util.ArrayList; import java.util.Date; import java.util.List; import junit.framework.AssertionFailedError; -import org.hibernate.dialect.CUBRIDDialect; -import org.hibernate.testing.SkipForDialect; -import org.junit.Test; import org.hibernate.QueryException; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.CUBRIDDialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.exception.ConstraintViolationException; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.IdentifierGenerator; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.FailureExpectedWithNewMetamodel; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.SkipLog; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.Test; /** * Tests execution of bulk UPDATE/DELETE statements through the new AST parser. @@ -1225,6 +1227,36 @@ public class BulkManipulationTest extends BaseCoreFunctionalTestCase { t.commit(); s.close(); } + + @Test + @TestForIssue( jiraKey = "HHH-1917" ) + public void testManyToManyBulkDelete() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + + Human friend = new Human(); + friend.setName( new Name( "Bob", 'B', "Bobbert" ) ); + s.save( friend ); + + Human brett = new Human(); + brett.setName( new Name( "Brett", 'E', "Meyer" ) ); + brett.setFriends( new ArrayList() ); + brett.getFriends().add( friend ); + s.save( brett ); + + s.flush(); + + try { + s.createQuery( "delete from Human" ).executeUpdate(); + } + catch (ConstraintViolationException cve) { + fail("The join table was not cleared prior to the bulk delete."); + } + finally { + t.rollback(); + s.close(); + } + } private class TestData { diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java index 3e5f713ab6..2790fde696 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java @@ -246,7 +246,7 @@ public class HQLTest extends QueryTranslatorTestCase { } @Test - @FailureExpected( jiraKey = "N/A" ) + @TestForIssue( jiraKey = "HHH-2045" ) public void testEmptyInList() { assertTranslation( "select a from Animal a where a.description in ()" ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java index ab2a026a42..4f953840bf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/HQLScrollFetchTest.java @@ -27,7 +27,8 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.transform.DistinctRootEntityResultTransformer; import org.junit.Test; - +@SkipForDialect( value = { Oracle8iDialect.class }, + comment = "Oracle does not support the identity column used in the mapping. Extended by NoIdentityHQLScrollFetchTest" ) public class HQLScrollFetchTest extends BaseCoreFunctionalTestCase { private static final String QUERY = "select p from Parent p join fetch p.children c"; @@ -342,7 +343,7 @@ public class HQLScrollFetchTest extends BaseCoreFunctionalTestCase { Transaction t = s.beginTransaction(); List list = s.createQuery( "from Parent" ).list(); for ( Iterator i = list.iterator(); i.hasNext(); ) { - s.delete( (Parent) i.next() ); + s.delete( i.next() ); } t.commit(); s.close(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/NoIdentityHQLScrollFetchTest.java b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/NoIdentityHQLScrollFetchTest.java new file mode 100644 index 0000000000..2da0681334 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/NoIdentityHQLScrollFetchTest.java @@ -0,0 +1,14 @@ +package org.hibernate.test.hqlfetchscroll; + +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.testing.RequiresDialect; + +@RequiresDialect( value = { Oracle8iDialect.class }, + comment = "Oracle does not support the identity column used in the HQLScrollFetchTest mapping." ) +public class NoIdentityHQLScrollFetchTest extends HQLScrollFetchTest { + + @Override + public String[] getMappings() { + return new String[] { "hqlfetchscroll/NoIdentityParentChild.hbm.xml" }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/NoIdentityParentChild.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/NoIdentityParentChild.hbm.xml new file mode 100644 index 0000000000..f42e84c81e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hqlfetchscroll/NoIdentityParentChild.hbm.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/AbstractEntityWithOneToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/AbstractEntityWithOneToManyTest.java index 553c98b90f..476924e558 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/AbstractEntityWithOneToManyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/AbstractEntityWithOneToManyTest.java @@ -66,23 +66,23 @@ public abstract class AbstractEntityWithOneToManyTest extends BaseCoreFunctional protected void prepareTest() throws Exception { super.prepareTest(); - isContractPartiesInverse = ( ( SessionFactoryImpl ) sessionFactory() ).getCollectionPersister( Contract.class.getName() + ".parties" ).isInverse(); + isContractPartiesInverse = sessionFactory().getCollectionPersister( Contract.class.getName() + ".parties" ).isInverse(); try { - ( ( SessionFactoryImpl ) sessionFactory() ).getEntityPersister( Party.class.getName() ).getPropertyType( "contract" ); + sessionFactory().getEntityPersister( Party.class.getName() ).getPropertyType( "contract" ); isContractPartiesBidirectional = true; } catch ( QueryException ex) { isContractPartiesBidirectional = false; } try { - ( ( SessionFactoryImpl ) sessionFactory() ).getEntityPersister( ContractVariation.class.getName() ).getPropertyType( "contract" ); + sessionFactory().getEntityPersister( ContractVariation.class.getName() ).getPropertyType( "contract" ); isContractVariationsBidirectional = true; } catch ( QueryException ex) { isContractVariationsBidirectional = false; } - isContractVersioned = ( ( SessionFactoryImpl ) sessionFactory() ).getEntityPersister( Contract.class.getName() ).isVersioned(); + isContractVersioned = sessionFactory().getEntityPersister( Contract.class.getName() ).isVersioned(); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/ContractVariation.java b/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/ContractVariation.java index 64cc2d26b1..d3188ae0e8 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/ContractVariation.java +++ b/hibernate-core/src/test/java/org/hibernate/test/immutable/entitywithmutablecollection/ContractVariation.java @@ -64,7 +64,6 @@ public class ContractVariation implements Serializable { public ContractVariation(int version, Contract contract) { this.contract = contract; - this.id = id; contract.getVariations().add(this); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/GeneralWorkTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/GeneralWorkTest.java index bac54c8db2..34e73d9c25 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/GeneralWorkTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/GeneralWorkTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import org.hibernate.JDBCException; import org.hibernate.Session; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -56,7 +57,7 @@ public class GeneralWorkTest extends BaseCoreFunctionalTestCase { @Test public void testGeneralUsage() throws Throwable { - Session session = openSession(); + final Session session = openSession(); session.beginTransaction(); session.doWork( new Work() { @@ -64,23 +65,24 @@ public class GeneralWorkTest extends BaseCoreFunctionalTestCase { // in this current form, users must handle try/catches themselves for proper resource release Statement statement = null; try { - statement = connection.createStatement(); + statement = ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); ResultSet resultSet = null; try { - resultSet = statement.executeQuery( "select * from T_JDBC_PERSON" ); + + resultSet = ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( statement, "select * from T_JDBC_PERSON" ); } finally { - releaseQuietly( resultSet ); + releaseQuietly( ((SessionImplementor)session), resultSet ); } try { - resultSet = statement.executeQuery( "select * from T_JDBC_BOAT" ); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( statement, "select * from T_JDBC_BOAT" ); } finally { - releaseQuietly( resultSet ); + releaseQuietly( ((SessionImplementor)session), resultSet ); } } finally { - releaseQuietly( statement ); + releaseQuietly( ((SessionImplementor)session), statement ); } } } @@ -91,7 +93,7 @@ public class GeneralWorkTest extends BaseCoreFunctionalTestCase { @Test public void testSQLExceptionThrowing() { - Session session = openSession(); + final Session session = openSession(); session.beginTransaction(); try { session.doWork( @@ -99,11 +101,11 @@ public class GeneralWorkTest extends BaseCoreFunctionalTestCase { public void execute(Connection connection) throws SQLException { Statement statement = null; try { - statement = connection.createStatement(); - statement.executeQuery( "select * from non_existent" ); + statement = ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( statement, "select * from non_existent" ); } finally { - releaseQuietly( statement ); + releaseQuietly( ((SessionImplementor)session), statement ); } } } @@ -125,36 +127,36 @@ public class GeneralWorkTest extends BaseCoreFunctionalTestCase { session.save( p ); session.getTransaction().commit(); - session = openSession(); - session.beginTransaction(); - long count = session.doReturningWork( + final Session session2 = openSession(); + session2.beginTransaction(); + long count = session2.doReturningWork( new ReturningWork() { public Long execute(Connection connection) throws SQLException { // in this current form, users must handle try/catches themselves for proper resource release Statement statement = null; long personCount = 0; try { - statement = connection.createStatement(); + statement = ((SessionImplementor)session2).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); ResultSet resultSet = null; try { - resultSet = statement.executeQuery( "select count(*) from T_JDBC_PERSON" ); + resultSet = ((SessionImplementor)session2).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( statement, "select count(*) from T_JDBC_PERSON" ); resultSet.next(); personCount = resultSet.getLong( 1 ); assertEquals( 1L, personCount ); } finally { - releaseQuietly( resultSet ); + releaseQuietly( ((SessionImplementor)session2), resultSet ); } } finally { - releaseQuietly( statement ); + releaseQuietly( ((SessionImplementor)session2), statement ); } return personCount; } } ); - session.getTransaction().commit(); - session.close(); + session2.getTransaction().commit(); + session2.close(); assertEquals( 1L, count ); session = openSession(); @@ -164,26 +166,26 @@ public class GeneralWorkTest extends BaseCoreFunctionalTestCase { session.close(); } - private void releaseQuietly(Statement statement) { + private void releaseQuietly(SessionImplementor s, Statement statement) { if ( statement == null ) { return; } try { - statement.close(); + s.getTransactionCoordinator().getJdbcCoordinator().release( statement ); } - catch ( SQLException e ) { + catch (Exception e) { // ignore } } - private void releaseQuietly(ResultSet resultSet) { + private void releaseQuietly(SessionImplementor s, ResultSet resultSet) { if ( resultSet == null ) { return; } try { - resultSet.close(); + s.getTransactionCoordinator().getJdbcCoordinator().release( resultSet ); } - catch ( SQLException e ) { + catch (Exception e) { // ignore } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/AggressiveReleaseTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/AggressiveReleaseTest.java similarity index 63% rename from hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/AggressiveReleaseTest.java rename to hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/AggressiveReleaseTest.java index 604d05a292..28f2f3884e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/AggressiveReleaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/AggressiveReleaseTest.java @@ -21,38 +21,40 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.jdbc.proxies; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.hibernate.ConnectionReleaseMode; -import org.hibernate.engine.jdbc.internal.LogicalConnectionImpl; -import org.hibernate.engine.jdbc.internal.proxy.ProxyBuilder; -import org.hibernate.test.common.BasicTestingJdbcServiceImpl; -import org.hibernate.test.common.JdbcConnectionAccessImpl; -import org.hibernate.test.common.JournalingConnectionObserver; -import org.hibernate.testing.junit4.BaseUnitTestCase; +package org.hibernate.test.jdbc.internal; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +import org.hibernate.ConnectionReleaseMode; +import org.hibernate.Session; +import org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl; +import org.hibernate.engine.jdbc.internal.LogicalConnectionImpl; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.test.common.BasicTestingJdbcServiceImpl; +import org.hibernate.test.common.JdbcConnectionAccessImpl; +import org.hibernate.test.common.JournalingConnectionObserver; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + /** * @author Steve Ebersole */ -public class AggressiveReleaseTest extends BaseUnitTestCase { +public class AggressiveReleaseTest extends BaseCoreFunctionalTestCase { + private BasicTestingJdbcServiceImpl services = new BasicTestingJdbcServiceImpl(); - - @Before - public void setUp() throws SQLException { + + @Override + protected void prepareTest() throws Exception { services.prepare( true ); Connection connection = null; @@ -80,9 +82,9 @@ public class AggressiveReleaseTest extends BaseUnitTestCase { } } } - - @After - public void tearDown() throws SQLException { + + @Override + protected void cleanupTest() throws Exception { Connection connection = null; Statement stmnt = null; try { @@ -109,29 +111,30 @@ public class AggressiveReleaseTest extends BaseUnitTestCase { services.release(); } - + @Test public void testBasicRelease() { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( - null, - ConnectionReleaseMode.AFTER_STATEMENT, - services , - new JdbcConnectionAccessImpl( services.getConnectionProvider() ) - ); - Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); + Session session = openSession(); + SessionImplementor sessionImpl = (SessionImplementor) session; + + LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, + ConnectionReleaseMode.AFTER_STATEMENT, services, new JdbcConnectionAccessImpl( + services.getConnectionProvider() ) ); + JdbcCoordinatorImpl jdbcCoord = new JdbcCoordinatorImpl( logicalConnection, + sessionImpl.getTransactionCoordinator() ); JournalingConnectionObserver observer = new JournalingConnectionObserver(); logicalConnection.addObserver( observer ); try { - PreparedStatement ps = proxiedConnection.prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); + PreparedStatement ps = jdbcCoord.getStatementPreparer().prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); ps.setLong( 1, 1 ); ps.setString( 2, "name" ); - ps.execute(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoord.getResultSetReturn().execute( ps ); + assertTrue( jdbcCoord.hasRegisteredResources() ); assertEquals( 1, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 0, observer.getPhysicalConnectionReleasedCount() ); - ps.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoord.release( ps ); + assertFalse( jdbcCoord.hasRegisteredResources() ); assertEquals( 1, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); } @@ -139,122 +142,126 @@ public class AggressiveReleaseTest extends BaseUnitTestCase { fail( "incorrect exception type : sqlexception" ); } finally { - logicalConnection.close(); + session.close(); } - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoord.hasRegisteredResources() ); } @Test public void testReleaseCircumventedByHeldResources() { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( - null, - ConnectionReleaseMode.AFTER_STATEMENT, - services, - new JdbcConnectionAccessImpl( services.getConnectionProvider() ) - ); - Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); + Session session = openSession(); + SessionImplementor sessionImpl = (SessionImplementor) session; + + LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, + ConnectionReleaseMode.AFTER_STATEMENT, services, new JdbcConnectionAccessImpl( + services.getConnectionProvider() ) ); + JdbcCoordinatorImpl jdbcCoord = new JdbcCoordinatorImpl( logicalConnection, + sessionImpl.getTransactionCoordinator() ); JournalingConnectionObserver observer = new JournalingConnectionObserver(); logicalConnection.addObserver( observer ); try { - PreparedStatement ps = proxiedConnection.prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); + PreparedStatement ps = jdbcCoord.getStatementPreparer().prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); ps.setLong( 1, 1 ); ps.setString( 2, "name" ); - ps.execute(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoord.getResultSetReturn().execute( ps ); + assertTrue( jdbcCoord.hasRegisteredResources() ); assertEquals( 1, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 0, observer.getPhysicalConnectionReleasedCount() ); - ps.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoord.release( ps ); + assertFalse( jdbcCoord.hasRegisteredResources() ); assertEquals( 1, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); - + // open a result set and hold it open... - ps = proxiedConnection.prepareStatement( "select * from SANDBOX_JDBC_TST" ); - ps.executeQuery(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + ps = jdbcCoord.getStatementPreparer().prepareStatement( "select * from SANDBOX_JDBC_TST" ); + jdbcCoord.getResultSetReturn().extract( ps ); + assertTrue( jdbcCoord.hasRegisteredResources() ); assertEquals( 2, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); - + // open a second result set - PreparedStatement ps2 = proxiedConnection.prepareStatement( "select * from SANDBOX_JDBC_TST" ); - ps2.execute(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + PreparedStatement ps2 = jdbcCoord.getStatementPreparer().prepareStatement( "select * from SANDBOX_JDBC_TST" ); + jdbcCoord.getResultSetReturn().execute( ps ); + assertTrue( jdbcCoord.hasRegisteredResources() ); assertEquals( 2, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); // and close it... - ps2.close(); + jdbcCoord.release( ps2 ); // the release should be circumvented... - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertTrue( jdbcCoord.hasRegisteredResources() ); assertEquals( 2, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); - + // let the close of the logical connection below release all resources (hopefully)... } catch ( SQLException sqle ) { fail( "incorrect exception type : sqlexception" ); } finally { - logicalConnection.close(); + jdbcCoord.close(); + session.close(); } - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoord.hasRegisteredResources() ); assertEquals( 2, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 2, observer.getPhysicalConnectionReleasedCount() ); } @Test public void testReleaseCircumventedManually() { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( - null, - ConnectionReleaseMode.AFTER_STATEMENT, - services, - new JdbcConnectionAccessImpl( services.getConnectionProvider() ) - ); - Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); + Session session = openSession(); + SessionImplementor sessionImpl = (SessionImplementor) session; + + LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( null, + ConnectionReleaseMode.AFTER_STATEMENT, services, new JdbcConnectionAccessImpl( + services.getConnectionProvider() ) ); + JdbcCoordinatorImpl jdbcCoord = new JdbcCoordinatorImpl( logicalConnection, + sessionImpl.getTransactionCoordinator() ); JournalingConnectionObserver observer = new JournalingConnectionObserver(); logicalConnection.addObserver( observer ); try { - PreparedStatement ps = proxiedConnection.prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); + PreparedStatement ps = jdbcCoord.getStatementPreparer().prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); ps.setLong( 1, 1 ); ps.setString( 2, "name" ); - ps.execute(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoord.getResultSetReturn().execute( ps ); + assertTrue( jdbcCoord.hasRegisteredResources() ); assertEquals( 1, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 0, observer.getPhysicalConnectionReleasedCount() ); - ps.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoord.release( ps ); + assertFalse( jdbcCoord.hasRegisteredResources() ); assertEquals( 1, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); - + // disable releases... - logicalConnection.disableReleases(); - + jdbcCoord.disableReleases(); + // open a result set... - ps = proxiedConnection.prepareStatement( "select * from SANDBOX_JDBC_TST" ); - ps.executeQuery(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + ps = jdbcCoord.getStatementPreparer().prepareStatement( "select * from SANDBOX_JDBC_TST" ); + jdbcCoord.getResultSetReturn().extract( ps ); + assertTrue( jdbcCoord.hasRegisteredResources() ); assertEquals( 2, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); // and close it... - ps.close(); + jdbcCoord.release( ps ); // the release should be circumvented... - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoord.hasRegisteredResources() ); assertEquals( 2, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 1, observer.getPhysicalConnectionReleasedCount() ); - + // let the close of the logical connection below release all resources (hopefully)... } catch ( SQLException sqle ) { fail( "incorrect exception type : sqlexception" ); } finally { - logicalConnection.close(); + jdbcCoord.close(); + session.close(); } - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoord.hasRegisteredResources() ); assertEquals( 2, observer.getPhysicalConnectionObtainedCount() ); assertEquals( 2, observer.getPhysicalConnectionReleasedCount() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/BasicConnectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/BasicConnectionTest.java new file mode 100644 index 0000000000..5c5befed5c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/BasicConnectionTest.java @@ -0,0 +1,110 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.jdbc.internal; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +import org.hibernate.JDBCException; +import org.hibernate.Session; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * @author Steve Ebersole + * @author Brett Meyer + */ +public class BasicConnectionTest extends BaseCoreFunctionalTestCase { + + @Test + public void testExceptionHandling() { + Session session = openSession(); + SessionImplementor sessionImpl = (SessionImplementor) session; + boolean caught = false; + try { + PreparedStatement ps = sessionImpl.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer() + .prepareStatement( "select count(*) from NON_EXISTENT" ); + sessionImpl.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().execute( ps ); + } + catch ( JDBCException ok ) { + caught = true; + } + finally { + session.close(); + } + + assertTrue( "The connection did not throw a JDBCException as expected", caught ); + } + + @Test + public void testBasicJdbcUsage() throws JDBCException { + Session session = openSession(); + SessionImplementor sessionImpl = (SessionImplementor) session; + JdbcCoordinator jdbcCoord = sessionImpl.getTransactionCoordinator().getJdbcCoordinator(); + + try { + Statement statement = jdbcCoord.getStatementPreparer().createStatement(); + String dropSql = getDialect().getDropTableString( "SANDBOX_JDBC_TST" ); + try { + jdbcCoord.getResultSetReturn().execute( statement, dropSql ); + } + catch ( Exception e ) { + // ignore if the DB doesn't support "if exists" and the table doesn't exist + } + jdbcCoord.getResultSetReturn().execute( statement, + "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); + assertTrue( jdbcCoord.hasRegisteredResources() ); + assertTrue( jdbcCoord.getLogicalConnection().isPhysicallyConnected() ); + jdbcCoord.release( statement ); + assertFalse( jdbcCoord.hasRegisteredResources() ); + assertTrue( jdbcCoord.getLogicalConnection().isPhysicallyConnected() ); // after_transaction specified + + PreparedStatement ps = jdbcCoord.getStatementPreparer().prepareStatement( + "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); + ps.setLong( 1, 1 ); + ps.setString( 2, "name" ); + jdbcCoord.getResultSetReturn().execute( ps ); + + ps = jdbcCoord.getStatementPreparer().prepareStatement( "select * from SANDBOX_JDBC_TST" ); + jdbcCoord.getResultSetReturn().extract( ps ); + + assertTrue( jdbcCoord.hasRegisteredResources() ); + } + catch ( SQLException e ) { + fail( "incorrect exception type : sqlexception" ); + } + finally { + session.close(); + } + + assertFalse( jdbcCoord.hasRegisteredResources() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BatchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/BatchingTest.java similarity index 66% rename from hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BatchingTest.java rename to hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/BatchingTest.java index 4666eb4c5f..0e9bb7a6a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BatchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jdbc/internal/BatchingTest.java @@ -21,18 +21,19 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.jdbc.proxies; +package org.hibernate.test.jdbc.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.Statement; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; +import org.hibernate.Session; import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; @@ -42,41 +43,20 @@ import org.hibernate.engine.jdbc.batch.spi.BatchBuilder; import org.hibernate.engine.jdbc.batch.spi.BatchKey; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; -import org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl; -import org.hibernate.engine.transaction.spi.TransactionContext; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.transaction.spi.TransactionCoordinator; import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectations; import org.hibernate.test.common.JournalingBatchObserver; import org.hibernate.test.common.JournalingTransactionObserver; -import org.hibernate.test.common.TransactionContextImpl; -import org.hibernate.test.common.TransactionEnvironmentImpl; -import org.hibernate.testing.env.ConnectionProviderBuilder; -import org.hibernate.testing.junit4.BaseUnitTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; /** * @author Steve Ebersole + * @author Brett Meyer */ -public class BatchingTest extends BaseUnitTestCase implements BatchKey { - private StandardServiceRegistryImpl serviceRegistry; - - @Before - public void setUp() throws Exception { - serviceRegistry = (StandardServiceRegistryImpl) new StandardServiceRegistryBuilder() - .applySettings( ConnectionProviderBuilder.getConnectionProviderProperties() ) - .build(); - } - - @After - public void tearDown() throws Exception { - serviceRegistry.destroy(); - } - +public class BatchingTest extends BaseCoreFunctionalTestCase implements BatchKey { @Override public int getBatchedStatementCount() { return 1; @@ -89,26 +69,30 @@ public class BatchingTest extends BaseUnitTestCase implements BatchKey { @Test public void testNonBatchingUsage() throws Exception { - final TransactionContext transactionContext = new TransactionContextImpl( - new TransactionEnvironmentImpl( serviceRegistry ) - ); - - TransactionCoordinatorImpl transactionCoordinator = new TransactionCoordinatorImpl( null, transactionContext ); + Session session = openSession(); + SessionImplementor sessionImpl = (SessionImplementor) session; + + TransactionCoordinator transactionCoordinator = sessionImpl.getTransactionCoordinator(); JournalingTransactionObserver observer = new JournalingTransactionObserver(); transactionCoordinator.addObserver( observer ); final JdbcCoordinator jdbcCoordinator = transactionCoordinator.getJdbcCoordinator(); LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection(); - Connection connection = logicalConnection.getShareableConnectionProxy(); // set up some tables to use - Statement statement = connection.createStatement(); - statement.execute( "drop table SANDBOX_JDBC_TST if exists" ); - statement.execute( "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + Statement statement = jdbcCoordinator.getStatementPreparer().createStatement(); + String dropSql = getDialect().getDropTableString( "SANDBOX_JDBC_TST" ); + try { + jdbcCoordinator.getResultSetReturn().execute( statement, dropSql ); + } + catch ( Exception e ) { + // ignore if the DB doesn't support "if exists" and the table doesn't exist + } + jdbcCoordinator.getResultSetReturn().execute( statement, "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); assertTrue( logicalConnection.isPhysicallyConnected() ); - statement.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoordinator.release( statement ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); assertTrue( logicalConnection.isPhysicallyConnected() ); // after_transaction specified // ok, now we can get down to it... @@ -134,45 +118,50 @@ public class BatchingTest extends BaseUnitTestCase implements BatchKey { insertBatch.addToBatch(); assertEquals( 0, batchObserver.getExplicitExecutionCount() ); assertEquals( 1, batchObserver.getImplicitExecutionCount() ); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); insertBatch.execute(); assertEquals( 1, batchObserver.getExplicitExecutionCount() ); assertEquals( 1, batchObserver.getImplicitExecutionCount() ); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); insertBatch.release(); txn.commit(); - logicalConnection.close(); + session.close(); } @Test public void testBatchingUsage() throws Exception { - final TransactionContext transactionContext = new TransactionContextImpl( new TransactionEnvironmentImpl( serviceRegistry ) ); - - TransactionCoordinatorImpl transactionCoordinator = new TransactionCoordinatorImpl( null, transactionContext ); - JournalingTransactionObserver transactionObserver = new JournalingTransactionObserver(); - transactionCoordinator.addObserver( transactionObserver ); + Session session = openSession(); + SessionImplementor sessionImpl = (SessionImplementor) session; + + TransactionCoordinator transactionCoordinator = sessionImpl.getTransactionCoordinator(); + JournalingTransactionObserver observer = new JournalingTransactionObserver(); + transactionCoordinator.addObserver( observer ); final JdbcCoordinator jdbcCoordinator = transactionCoordinator.getJdbcCoordinator(); LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection(); - Connection connection = logicalConnection.getShareableConnectionProxy(); // set up some tables to use - Statement statement = connection.createStatement(); - statement.execute( "drop table SANDBOX_JDBC_TST if exists" ); - statement.execute( "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + Statement statement = jdbcCoordinator.getStatementPreparer().createStatement(); + String dropSql = getDialect().getDropTableString( "SANDBOX_JDBC_TST" ); + try { + jdbcCoordinator.getResultSetReturn().execute( statement, dropSql ); + } + catch ( Exception e ) { + // ignore if the DB doesn't support "if exists" and the table doesn't exist + } jdbcCoordinator.getResultSetReturn().execute( statement, "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); assertTrue( logicalConnection.isPhysicallyConnected() ); - statement.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoordinator.release( statement ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); assertTrue( logicalConnection.isPhysicallyConnected() ); // after_transaction specified // ok, now we can get down to it... TransactionImplementor txn = transactionCoordinator.getTransaction(); // same as Session#getTransaction txn.begin(); - assertEquals( 1, transactionObserver.getBegins() ); + assertEquals( 1, observer.getBegins() ); final BatchBuilder batchBuilder = new BatchBuilderImpl( 2 ); final BatchKey batchKey = new BasicBatchKey( "this", Expectations.BASIC ); @@ -192,7 +181,7 @@ public class BatchingTest extends BaseUnitTestCase implements BatchKey { insertBatch.addToBatch(); assertEquals( 0, batchObserver.getExplicitExecutionCount() ); assertEquals( 0, batchObserver.getImplicitExecutionCount() ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); PreparedStatement insert2 = insertBatch.getBatchStatement( insertSql, false ); assertSame( insert, insert2 ); @@ -204,17 +193,17 @@ public class BatchingTest extends BaseUnitTestCase implements BatchKey { insertBatch.addToBatch(); assertEquals( 0, batchObserver.getExplicitExecutionCount() ); assertEquals( 1, batchObserver.getImplicitExecutionCount() ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); insertBatch.execute(); assertEquals( 1, batchObserver.getExplicitExecutionCount() ); assertEquals( 1, batchObserver.getImplicitExecutionCount() ); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); insertBatch.release(); txn.commit(); - logicalConnection.close(); + session.close(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BasicConnectionProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BasicConnectionProxyTest.java deleted file mode 100644 index 8fc9be1383..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/jdbc/proxies/BasicConnectionProxyTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.test.jdbc.proxies; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.hibernate.ConnectionReleaseMode; -import org.hibernate.JDBCException; -import org.hibernate.engine.jdbc.internal.LogicalConnectionImpl; -import org.hibernate.engine.jdbc.internal.proxy.ProxyBuilder; -import org.hibernate.test.common.BasicTestingJdbcServiceImpl; -import org.hibernate.test.common.JdbcConnectionAccessImpl; -import org.hibernate.testing.junit4.BaseUnitTestCase; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * @author Steve Ebersole - */ -public class BasicConnectionProxyTest extends BaseUnitTestCase { - private BasicTestingJdbcServiceImpl services = new BasicTestingJdbcServiceImpl(); - - @Before - public void setUp() { - services.prepare( false ); - } - - @After - public void tearDown() { - services.release(); - } - - @Test - public void testDatabaseMetaDataHandling() throws Throwable { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( - null, - ConnectionReleaseMode.AFTER_TRANSACTION, - services, - new JdbcConnectionAccessImpl( services.getConnectionProvider() ) - ); - Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); - try { - DatabaseMetaData metaData = proxiedConnection.getMetaData(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - ResultSet rs1 = metaData.getCatalogs(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - rs1.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - metaData.getCatalogs(); - metaData.getSchemas(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - } - catch ( SQLException e ) { - fail( "incorrect exception type : sqlexception" ); - } - finally { - logicalConnection.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - } - } - - @Test - public void testExceptionHandling() { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( - null, - ConnectionReleaseMode.AFTER_TRANSACTION, - services, - new JdbcConnectionAccessImpl( services.getConnectionProvider() ) - ); - Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); - try { - proxiedConnection.prepareStatement( "select count(*) from NON_EXISTENT" ).executeQuery(); - } - catch ( SQLException e ) { - fail( "incorrect exception type : sqlexception" ); - } - catch ( JDBCException ok ) { - // expected outcome - } - finally { - logicalConnection.close(); - } - } - - @Test - public void testBasicJdbcUsage() throws JDBCException { - LogicalConnectionImpl logicalConnection = new LogicalConnectionImpl( - null, - ConnectionReleaseMode.AFTER_TRANSACTION, - services, - new JdbcConnectionAccessImpl( services.getConnectionProvider() ) - ); - Connection proxiedConnection = ProxyBuilder.buildConnection( logicalConnection ); - - try { - Statement statement = proxiedConnection.createStatement(); - statement.execute( "drop table SANDBOX_JDBC_TST if exists" ); - statement.execute( "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertTrue( logicalConnection.isPhysicallyConnected() ); - statement.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertTrue( logicalConnection.isPhysicallyConnected() ); // after_transaction specified - - PreparedStatement ps = proxiedConnection.prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); - ps.setLong( 1, 1 ); - ps.setString( 2, "name" ); - ps.execute(); - - ps = proxiedConnection.prepareStatement( "select * from SANDBOX_JDBC_TST" ); - ps.executeQuery(); - - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - } - catch ( SQLException e ) { - fail( "incorrect exception type : sqlexception" ); - } - finally { - logicalConnection.close(); - } - - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/join/JoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/join/JoinTest.java index cdc960a454..43a96cf9db 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/join/JoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/join/JoinTest.java @@ -23,24 +23,25 @@ */ package org.hibernate.test.join; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.sql.Connection; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Iterator; import java.util.List; -import org.junit.Test; - import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.criterion.Restrictions; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.AbstractWork; import org.hibernate.testing.FailureExpectedWithNewMetamodel; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.Test; /** * @author Gavin King @@ -141,14 +142,7 @@ public class JoinTest extends BaseCoreFunctionalTestCase { s.clear(); // Remove the optional row from the join table and requery the User obj - s.doWork( - new AbstractWork() { - @Override - public void execute(Connection connection) throws SQLException { - connection.prepareStatement("delete from t_user").execute(); - } - } - ); + doWork(s); s.clear(); jesus = (User) s.get( Person.class, new Long( jesus.getId() ) ); @@ -162,6 +156,18 @@ public class JoinTest extends BaseCoreFunctionalTestCase { s.close(); } + private void doWork(final Session s) { + s.doWork( + new AbstractWork() { + @Override + public void execute(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement( "delete from t_user" ); + ps.execute(); + } + } + ); + } + @Test public void testCustomColumnReadAndWrite() { Session s = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/naturalid/ClassWithIdentityColumn.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/naturalid/ClassWithIdentityColumn.java new file mode 100644 index 0000000000..6b8f8d3415 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/naturalid/ClassWithIdentityColumn.java @@ -0,0 +1,27 @@ +package org.hibernate.test.jpa.naturalid; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.annotations.NaturalId; + +@Entity +public class ClassWithIdentityColumn { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @NaturalId(mutable = true) + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/naturalid/MutableNaturalIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/naturalid/MutableNaturalIdTest.java index 52c88b7e1f..02eab2c484 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/jpa/naturalid/MutableNaturalIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/naturalid/MutableNaturalIdTest.java @@ -23,20 +23,25 @@ */ package org.hibernate.test.jpa.naturalid; -import org.junit.Test; - -import org.hibernate.Session; -import org.hibernate.test.jpa.AbstractJPATest; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import org.hibernate.Session; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.test.jpa.AbstractJPATest; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + /** * @author Steve Ebersole */ +@SkipForDialect(value = Oracle8iDialect.class, + comment = "Oracle does not support identity key generation") public class MutableNaturalIdTest extends AbstractJPATest { @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Group.class }; + return new Class[] { Group.class, ClassWithIdentityColumn.class }; } @Test @@ -67,4 +72,19 @@ public class MutableNaturalIdTest extends AbstractJPATest { s.getTransaction().commit(); s.close(); } + + @Test + @TestForIssue( jiraKey = "HHH-7304") + public void testInLineSynchWithIdentityColumn() { + Session s = openSession(); + s.beginTransaction(); + ClassWithIdentityColumn e = new ClassWithIdentityColumn(); + e.setName("Dampf"); + s.save(e); + e.setName("Klein"); + assertNotNull(session.bySimpleNaturalId(ClassWithIdentityColumn.class).load("Klein")); + + session.getTransaction().rollback(); + session.close(); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/DestinationEntity.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/DestinationEntity.java new file mode 100644 index 0000000000..56963ab3f4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/DestinationEntity.java @@ -0,0 +1,99 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.jpa.ql; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedNativeQueries; +import javax.persistence.NamedNativeQuery; +import javax.persistence.Table; + +/** + * @author Janario Oliveira + */ +@Entity +@Table(name = "destination_entity") +@NamedNativeQueries({ + @NamedNativeQuery(name = "DestinationEntity.insertSelect", query = "insert into destination_entity(id, from_id, fullNameFrom) " + + " select fe.id, fe.id, fe.name||fe.lastName from from_entity fe where fe.id in (:ids)"), + @NamedNativeQuery(name = "DestinationEntity.insert", query = "insert into destination_entity(id, from_id, fullNameFrom) " + + "values (:generatedId, :fromId, :fullName)"), + @NamedNativeQuery(name = "DestinationEntity.update", query = "update destination_entity set from_id=:idFrom, fullNameFrom=:fullName" + + " where id in (:ids)"), + @NamedNativeQuery(name = "DestinationEntity.delete", query = "delete from destination_entity where id in (:ids)"), + @NamedNativeQuery(name = "DestinationEntity.selectIds", query = "select id, from_id, fullNameFrom from destination_entity where id in (:ids)") }) +public class DestinationEntity { + + @Id + @GeneratedValue + Integer id; + @ManyToOne(optional = false) + @JoinColumn(name = "from_id") + FromEntity from; + @Column(nullable = false) + String fullNameFrom; + + public DestinationEntity() { + } + + public DestinationEntity(FromEntity from, String fullNameFrom) { + this.from = from; + this.fullNameFrom = fullNameFrom; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( from == null ) ? 0 : from.hashCode() ); + result = prime * result + ( ( fullNameFrom == null ) ? 0 : fullNameFrom.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( getClass() != obj.getClass() ) + return false; + DestinationEntity other = (DestinationEntity) obj; + if ( from == null ) { + if ( other.from != null ) + return false; + } + else if ( !from.equals( other.from ) ) + return false; + if ( fullNameFrom == null ) { + if ( other.fullNameFrom != null ) + return false; + } + else if ( !fullNameFrom.equals( other.fullNameFrom ) ) + return false; + return true; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/FromEntity.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/FromEntity.java new file mode 100644 index 0000000000..ffd305a82c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/FromEntity.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.jpa.ql; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Janario Oliveira + */ +@Entity +@Table(name = "from_entity") +public class FromEntity { + + @Id + @GeneratedValue + Integer id; + @Column(nullable = false) + String name; + @Column(nullable = false) + String lastName; + + public FromEntity() { + } + + public FromEntity(String name, String lastName) { + this.name = name; + this.lastName = lastName; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 53 * hash + ( this.name != null ? this.name.hashCode() : 0 ); + hash = 53 * hash + ( this.lastName != null ? this.lastName.hashCode() : 0 ); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final FromEntity other = (FromEntity) obj; + if ( ( this.name == null ) ? ( other.name != null ) : !this.name.equals( other.name ) ) { + return false; + } + if ( ( this.lastName == null ) ? ( other.lastName != null ) : !this.lastName.equals( other.lastName ) ) { + return false; + } + return true; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/NamedNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/NamedNativeQueryTest.java new file mode 100644 index 0000000000..6504468285 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/ql/NamedNativeQueryTest.java @@ -0,0 +1,328 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.jpa.ql; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.SkipForDialects; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * @author Janario Oliveira + */ +public class NamedNativeQueryTest extends BaseCoreFunctionalTestCase { + + private FromEntity createFrom(String name, String lastName) { + Session session = openSession(); + session.getTransaction().begin(); + FromEntity fromEntity = new FromEntity( name, lastName ); + session.save( fromEntity ); + session.getTransaction().commit(); + session.close(); + return fromEntity; + } + + private DestinationEntity createDestination(FromEntity fromEntity, String fullName) { + final DestinationEntity destinationEntity = new DestinationEntity( fromEntity, fullName ); + + Session session = openSession(); + session.getTransaction().begin(); + session.save( destinationEntity ); + session.getTransaction().commit(); + session.close(); + return destinationEntity; + } + + @SuppressWarnings("unchecked") + private List findDestinationByIds(List ids) { + Session session = openSession(); + List list = session + .createQuery( "from DestinationEntity de where de.id in (:ids) order by id" ) + .setParameterList( "ids", ids ).list(); + session.close(); + return list; + } + + @Test + public void testSingleSelect() { + final String name = "Name"; + final String lastName = "LastName"; + final String fullName = name + " " + lastName; + final DestinationEntity destination = createDestination( createFrom( name, lastName ), fullName ); + + Session session = openSession(); + Query select = session.getNamedQuery( "DestinationEntity.selectIds" ); + select.setParameterList( "ids", Collections.singletonList( destination.id ) ); + Object[] unique = (Object[]) select.uniqueResult(); + session.close(); + + // Compare the Strings, not the actual IDs. Can come back as, for ex, + // a BigDecimal in Oracle. + assertEquals( destination.id + "", unique[0] + "" ); + assertEquals( destination.from.id + "", unique[1] + "" ); + assertEquals( destination.fullNameFrom, unique[2] ); + } + + @Test + public void testMultipleSelect() { + final String name = "Name"; + final String lastName = "LastName"; + final List ids = new ArrayList(); + final int quantity = 10; + final List destinations = new ArrayList(); + for ( int i = 0; i < quantity; i++ ) { + DestinationEntity createDestination = createDestination( createFrom( name + i, lastName + i ), name + i + + lastName + i ); + ids.add( createDestination.id ); + destinations.add( createDestination ); + } + + Session session = openSession(); + Query select = session.getNamedQuery( "DestinationEntity.selectIds" ); + select.setParameterList( "ids", ids ); + List list = select.list(); + session.close(); + + assertEquals( quantity, list.size() ); + for ( int i = 0; i < list.size(); i++ ) { + Object[] object = (Object[]) list.get( i ); + DestinationEntity destination = destinations.get( i ); + // Compare the Strings, not the actual IDs. Can come back as, for ex, + // a BigDecimal in Oracle. + assertEquals( destination.id + "", object[0] + "" ); + assertEquals( destination.from.id + "", object[1] + "" ); + assertEquals( destination.fullNameFrom, object[2] ); + } + } + + @Test + public void testInsertSingleValue() { + final String name = "Name"; + final String lastName = "LastName"; + final String fullName = name + " " + lastName; + final FromEntity fromEntity = createFrom( name, lastName ); + final int id = 10000;// id fake + + Session session = openSession(); + session.getTransaction().begin(); + Query insert = session.getNamedQuery( "DestinationEntity.insert" ); + insert.setParameter( "generatedId", id ); + insert.setParameter( "fromId", fromEntity.id ); + insert.setParameter( "fullName", fullName ); + int executeUpdate = insert.executeUpdate(); + assertEquals( 1, executeUpdate ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + DestinationEntity get = (DestinationEntity) session.get( DestinationEntity.class, id ); + session.close(); + + assertEquals( fromEntity, get.from ); + assertEquals( fullName, get.fullNameFrom ); + } + + @Test + @SkipForDialects( { + @SkipForDialect( value = MySQLDialect.class, comment = "MySQL appears to have trouble with fe.id selected twice in one statement"), + @SkipForDialect( value = SQLServerDialect.class, comment = "SQL Server does not support the || operator.") + } ) + // TODO: Re-form DestinationEntity.insertSelect to something more supported? + public void testInsertMultipleValues() { + final String name = "Name"; + final String lastName = "LastName"; + final List ids = new ArrayList(); + final int quantity = 10; + final List froms = new ArrayList(); + for ( int i = 0; i < quantity; i++ ) { + FromEntity fe = createFrom( name + i, lastName + i ); + froms.add( fe ); + ids.add( fe.id ); + } + + Session session = openSession(); + session.getTransaction().begin(); + Query insertSelect = session.getNamedQuery( "DestinationEntity.insertSelect" ); + insertSelect.setParameterList( "ids", ids ); + int executeUpdate = insertSelect.executeUpdate(); + assertEquals( quantity, executeUpdate ); + + session.getTransaction().commit(); + session.close(); + + List list = findDestinationByIds( ids ); + assertEquals( quantity, list.size() ); + + for ( int i = 0; i < quantity; i++ ) { + DestinationEntity de = (DestinationEntity) list.get( i ); + FromEntity from = froms.get( i ); + assertEquals( from, de.from ); + assertEquals( from.name + from.lastName, de.fullNameFrom ); + } + } + + @Test + public void testUpdateSingleValue() { + final String name = "Name"; + final String lastName = "LastName"; + final String fullName = name + " " + lastName; + + final FromEntity fromEntity = createFrom( name, lastName ); + final DestinationEntity destinationEntity = createDestination( fromEntity, fullName ); + + final String inverseFullName = lastName + " " + name; + final FromEntity anotherFrom = createFrom( lastName, name ); + + Session session = openSession(); + session.getTransaction().begin(); + Query update = session.getNamedQuery( "DestinationEntity.update" ); + update.setParameter( "idFrom", anotherFrom.id ); + update.setParameter( "fullName", inverseFullName ); + update.setParameterList( "ids", Collections.singletonList( destinationEntity.id ) ); + + int executeUpdate = update.executeUpdate(); + assertEquals( 1, executeUpdate ); + + session.getTransaction().commit(); + session.close(); + + session = openSession(); + DestinationEntity get = (DestinationEntity) session.get( DestinationEntity.class, destinationEntity.id ); + + assertEquals( anotherFrom, get.from ); + assertEquals( inverseFullName, get.fullNameFrom ); + session.close(); + } + + @Test + public void testUpdateMultipleValues() { + final String name = "Name"; + final String lastName = "LastName"; + final List ids = new ArrayList(); + final int quantity = 10; + final List destinations = new ArrayList(); + for ( int i = 0; i < quantity; i++ ) { + FromEntity fe = createFrom( name + i, lastName + i ); + DestinationEntity destination = createDestination( fe, fe.name + fe.lastName ); + destinations.add( destination ); + ids.add( destination.id ); + } + + final String inverseFullName = lastName + " " + name; + final FromEntity anotherFrom = createFrom( lastName, name ); + + Session session = openSession(); + session.getTransaction().begin(); + Query update = session.getNamedQuery( "DestinationEntity.update" ); + update.setParameter( "idFrom", anotherFrom.id ); + update.setParameter( "fullName", inverseFullName ); + update.setParameterList( "ids", ids ); + + int executeUpdate = update.executeUpdate(); + assertEquals( quantity, executeUpdate ); + + session.getTransaction().commit(); + session.close(); + + List list = findDestinationByIds( ids ); + assertEquals( quantity, list.size() ); + + for ( int i = 0; i < quantity; i++ ) { + DestinationEntity updated = (DestinationEntity) list.get( i ); + + assertEquals( anotherFrom, updated.from ); + assertEquals( inverseFullName, updated.fullNameFrom ); + } + } + + @Test + public void testDeleteSingleValue() { + final String name = "Name"; + final String lastName = "LastName"; + final String fullName = name + " " + lastName; + + final FromEntity fromEntity = createFrom( name, lastName ); + final DestinationEntity destinationEntity = createDestination( fromEntity, fullName ); + + Session session = openSession(); + session.getTransaction().begin(); + Query delete = session.getNamedQuery( "DestinationEntity.delete" ); + delete.setParameterList( "ids", Collections.singletonList( destinationEntity.id ) ); + + int executeUpdate = delete.executeUpdate(); + assertEquals( 1, executeUpdate ); + + session.getTransaction().commit(); + session.close(); + + session = openSession(); + DestinationEntity get = (DestinationEntity) session.get( DestinationEntity.class, destinationEntity.id ); + session.close(); + + assertNull( get ); + } + + @Test + public void testDeleteMultipleValues() { + final String name = "Name"; + final String lastName = "LastName"; + final List ids = new ArrayList(); + final int quantity = 10; + final List destinations = new ArrayList(); + for ( int i = 0; i < quantity; i++ ) { + FromEntity fe = createFrom( name + i, lastName + i ); + DestinationEntity destination = createDestination( fe, fe.name + fe.lastName ); + destinations.add( destination ); + ids.add( destination.id ); + } + + Session session = openSession(); + session.getTransaction().begin(); + Query delete = session.getNamedQuery( "DestinationEntity.delete" ); + delete.setParameterList( "ids", ids ); + + int executeUpdate = delete.executeUpdate(); + assertEquals( quantity, executeUpdate ); + + session.getTransaction().commit(); + session.close(); + + List list = findDestinationByIds( ids ); + assertTrue( list.isEmpty() ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { FromEntity.class, DestinationEntity.class }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java index 5af456377e..28327cb3d5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.fail; import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; import java.sql.Time; import java.util.ArrayList; import java.util.Collection; @@ -83,6 +84,7 @@ import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.dialect.TimesTenDialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.internal.util.SerializationHelper; @@ -2091,29 +2093,29 @@ public class FooBarTest extends LegacyTestCase { s.getTransaction().commit(); s.close(); - s = openSession(); - s.beginTransaction(); - baz = (Baz) s.load( Baz.class, baz.getCode() ); + final Session s2 = openSession(); + s2.beginTransaction(); + baz = (Baz) s2.load( Baz.class, baz.getCode() ); assertTrue( baz.getFooArray().length == 1 ); - assertTrue( s.createQuery( "from Baz baz join baz.fooArray foo" ).list().size()==1 ); - assertTrue( s.createQuery( "from Foo foo" ).list().size()==2 ); - assertTrue( s.createFilter( baz.getFooArray(), "" ).list().size() == 1 ); + assertTrue( s2.createQuery( "from Baz baz join baz.fooArray foo" ).list().size()==1 ); + assertTrue( s2.createQuery( "from Foo foo" ).list().size()==2 ); + assertTrue( s2.createFilter( baz.getFooArray(), "" ).list().size() == 1 ); //assertTrue( s.delete("from java.lang.Object o")==9 ); - doDelete( s, "from Foo foo" ); + doDelete( s2, "from Foo foo" ); final String bazid = baz.getCode(); - s.delete( baz ); - int rows = s.doReturningWork( + s2.delete( baz ); + int rows = s2.doReturningWork( new AbstractReturningWork() { @Override public Integer execute(Connection connection) throws SQLException { - return connection.createStatement() - .executeUpdate( "delete from FOO_ARRAY where id_='" + bazid + "' and i>=8" ); + Statement st = connection.createStatement(); + return st.executeUpdate( "delete from FOO_ARRAY where id_='" + bazid + "' and i>=8" ); } } ); assertTrue( rows == 1 ); - s.getTransaction().commit(); - s.close(); + s2.getTransaction().commit(); + s2.close(); } @Test @@ -2655,45 +2657,46 @@ public class FooBarTest extends LegacyTestCase { assertTrue( baz.getCascadingBars().size()==1 ); txn.commit(); - s2 = (Session) SerializationHelper.deserialize( SerializationHelper.serialize(s) ); + final Session s3 = (Session) SerializationHelper.deserialize( SerializationHelper.serialize(s) ); s.close(); - txn2 = s2.beginTransaction(); - baz = (Baz) s2.load(Baz.class, baz.getCode()); - assertEquals( 3, ((Long) s2.createQuery( "select count(*) from Bar" ).iterate().next()).longValue() ); - s2.delete(baz); - s2.delete( baz.getTopGlarchez().get( Character.valueOf('G') ) ); - s2.delete( baz.getTopGlarchez().get( Character.valueOf('H') ) ); - int rows = s2.doReturningWork( + txn2 = s3.beginTransaction(); + baz = (Baz) s3.load(Baz.class, baz.getCode()); + assertEquals( 3, ((Long) s3.createQuery( "select count(*) from Bar" ).iterate().next()).longValue() ); + s3.delete(baz); + s3.delete( baz.getTopGlarchez().get( Character.valueOf('G') ) ); + s3.delete( baz.getTopGlarchez().get( Character.valueOf('H') ) ); + int rows = s3.doReturningWork( new AbstractReturningWork() { @Override public Integer execute(Connection connection) throws SQLException { final String sql = "update " + getDialect().openQuote() + "glarchez" + getDialect().closeQuote() + " set baz_map_id=null where baz_map_index='a'"; - return connection.createStatement().executeUpdate( sql ); + Statement st = connection.createStatement(); + return st.executeUpdate( sql ); } } ); assertTrue(rows==1); - assertEquals( 2, doDelete( s2, "from Bar bar" ) ); + assertEquals( 2, doDelete( s3, "from Bar bar" ) ); FooProxy[] arr = baz.getFooArray(); assertTrue( "new array of objects", arr.length==4 && arr[1].getKey().equals( foo.getKey() ) ); for ( int i=1; i-1 and s.name is null" ).list().size()==2 ); - s.createQuery( "select c from SubMulti sm join sm.children c" ).list(); - assertTrue( s.createQuery( "select elements(sm.children) from SubMulti as sm" ).list().size()==2 ); + s2.createQuery( "select c from SubMulti sm join sm.children c" ).list(); + assertTrue( s2.createQuery( "select elements(sm.children) from SubMulti as sm" ).list().size()==2 ); assertTrue( - s.createQuery( + s2.createQuery( "select distinct sm from SubMulti as sm join sm.children as s where s.amount>-1 and s.name is null" ).list().size()==1 ); - sm = (SubMulti) s.load(SubMulti.class, id); + sm = (SubMulti) s2.load(SubMulti.class, id); assertTrue( sm.getChildren().size()==2 ); assertEquals( - s.createFilter( sm.getMoreChildren(), "select count(*) where this.amount>-1 and this.name is null" ).list().get(0), + s2.createFilter( sm.getMoreChildren(), "select count(*) where this.amount>-1 and this.name is null" ).list().get(0), new Long(2) ); assertEquals( "FOO", sm.getDerived() ); assertSame( - s.createQuery( "select distinct s from SubMulti s where s.moreChildren[1].amount < 1.0" ).iterate().next(), + s2.createQuery( "select distinct s from SubMulti s where s.moreChildren[1].amount < 1.0" ).iterate().next(), sm ); assertTrue( sm.getMoreChildren().size()==2 ); - s.delete(sm); + s2.delete(sm); Iterator iter = sm.getChildren().iterator(); while ( iter.hasNext() ) { - s.delete( iter.next() ); + s2.delete( iter.next() ); } - s.flush(); - s.getTransaction().commit(); - s.close(); + s2.flush(); + s2.getTransaction().commit(); + s2.close(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/MultiplicityType.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/MultiplicityType.java index 2fec830ad7..db5c9336a0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/MultiplicityType.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/MultiplicityType.java @@ -57,8 +57,8 @@ public class MultiplicityType implements CompositeUserType { public Object getPropertyValue(Object component, int property) { Multiplicity o = (Multiplicity) component; return property==0 ? - (Object) new Integer(o.count) : - (Object) o.glarch; + new Integer(o.count) : + o.glarch; } public void setPropertyValue( @@ -94,7 +94,7 @@ public class MultiplicityType implements CompositeUserType { public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { - Integer c = (Integer) IntegerType.INSTANCE.nullSafeGet( rs, names[0], session ); + Integer c = IntegerType.INSTANCE.nullSafeGet( rs, names[0], session ); GlarchProxy g = (GlarchProxy) ( (Session) session ).getTypeHelper().entity( Glarch.class ).nullSafeGet(rs, names[1], session, owner); Multiplicity m = new Multiplicity(); m.count = c==null ? 0 : c.intValue(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java index 349e8ca5e3..5b560f8104 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/ParentChildTest.java @@ -1097,10 +1097,13 @@ public class ParentChildTest extends LegacyTestCase { s3.setCount(3); Simple s4 = new Simple( Long.valueOf(4) ); s4.setCount(4); + Simple s5 = new Simple( Long.valueOf(5) ); + s5.setCount(5); s.save( s1 ); s.save( s2 ); s.save( s3 ); s.save( s4 ); + s.save( s5 ); assertTrue( s.getCurrentLockMode(s1)==LockMode.WRITE ); tx.commit(); s.close(); @@ -1115,6 +1118,8 @@ public class ParentChildTest extends LegacyTestCase { assertTrue( s.getCurrentLockMode(s3)==LockMode.UPGRADE ); s4 = (Simple) s.get(Simple.class, new Long(4), LockMode.UPGRADE_NOWAIT); assertTrue( s.getCurrentLockMode(s4)==LockMode.UPGRADE_NOWAIT ); + s5 = (Simple) s.get(Simple.class, new Long(5), LockMode.UPGRADE_SKIPLOCKED); + assertTrue( s.getCurrentLockMode(s5)==LockMode.UPGRADE_SKIPLOCKED ); s1 = (Simple) s.load(Simple.class, new Long(1), LockMode.UPGRADE); //upgrade assertTrue( s.getCurrentLockMode(s1)==LockMode.UPGRADE ); @@ -1124,6 +1129,8 @@ public class ParentChildTest extends LegacyTestCase { assertTrue( s.getCurrentLockMode(s3)==LockMode.UPGRADE ); s4 = (Simple) s.load(Simple.class, new Long(4), LockMode.UPGRADE); assertTrue( s.getCurrentLockMode(s4)==LockMode.UPGRADE_NOWAIT ); + s5 = (Simple) s.load(Simple.class, new Long(5), LockMode.UPGRADE); + assertTrue( s.getCurrentLockMode(s5)==LockMode.UPGRADE_SKIPLOCKED ); s.lock(s2, LockMode.UPGRADE); //upgrade assertTrue( s.getCurrentLockMode(s2)==LockMode.UPGRADE ); @@ -1131,7 +1138,9 @@ public class ParentChildTest extends LegacyTestCase { assertTrue( s.getCurrentLockMode(s3)==LockMode.UPGRADE ); s.lock(s1, LockMode.UPGRADE_NOWAIT); s.lock(s4, LockMode.NONE); + s.lock(s5, LockMode.UPGRADE_SKIPLOCKED); assertTrue( s.getCurrentLockMode(s4)==LockMode.UPGRADE_NOWAIT ); + assertTrue( s.getCurrentLockMode(s5)==LockMode.UPGRADE_SKIPLOCKED ); tx.commit(); tx = s.beginTransaction(); @@ -1140,6 +1149,7 @@ public class ParentChildTest extends LegacyTestCase { assertTrue( s.getCurrentLockMode(s1)==LockMode.NONE ); assertTrue( s.getCurrentLockMode(s2)==LockMode.NONE ); assertTrue( s.getCurrentLockMode(s4)==LockMode.NONE ); + assertTrue( s.getCurrentLockMode(s5)==LockMode.NONE ); s.lock(s1, LockMode.READ); //upgrade assertTrue( s.getCurrentLockMode(s1)==LockMode.READ ); @@ -1161,8 +1171,9 @@ public class ParentChildTest extends LegacyTestCase { assertTrue( s.getCurrentLockMode(s1)==LockMode.NONE ); assertTrue( s.getCurrentLockMode(s2)==LockMode.NONE ); assertTrue( s.getCurrentLockMode(s4)==LockMode.NONE ); + assertTrue( s.getCurrentLockMode(s5)==LockMode.NONE ); - s.delete(s1); s.delete(s2); s.delete(s3); s.delete(s4); + s.delete(s1); s.delete(s2); s.delete(s3); s.delete(s4); s.delete(s5); tx.commit(); s.close(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/nationalized/SimpleNationalizedTest.java b/hibernate-core/src/test/java/org/hibernate/test/nationalized/SimpleNationalizedTest.java new file mode 100644 index 0000000000..6dfcd164e5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/nationalized/SimpleNationalizedTest.java @@ -0,0 +1,117 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.nationalized; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import java.sql.NClob; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Lob; + +import org.hibernate.annotations.Nationalized; +import org.hibernate.annotations.Type; +import org.hibernate.cfg.Configuration; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.type.CharacterNCharType; +import org.hibernate.type.MaterializedNClobType; +import org.hibernate.type.NClobType; +import org.hibernate.type.NTextType; +import org.hibernate.type.StringNVarcharType; +import org.junit.Test; + +/** + * @author Steve Ebersole + */ +public class SimpleNationalizedTest extends BaseUnitTestCase { + + @Entity( name="NationalizedEntity") + public static class NationalizedEntity { + @Id + private Integer id; + + @Nationalized + private String nvarcharAtt; + + @Lob + @Nationalized + private String materializedNclobAtt; + + @Lob + @Nationalized + private NClob nclobAtt; + + @Nationalized + private Character ncharacterAtt; + + @Nationalized + private Character[] ncharArrAtt; + + @Type(type = "ntext") + private String nlongvarcharcharAtt; + } + + @Test + public void simpleNationalizedTest() { + Configuration cfg = new Configuration(); + cfg.addAnnotatedClass( NationalizedEntity.class ); + cfg.buildMappings(); + PersistentClass pc = cfg.getClassMapping( NationalizedEntity.class.getName() ); + assertNotNull( pc ); + + { + Property prop = pc.getProperty( "nvarcharAtt" ); + assertSame( StringNVarcharType.INSTANCE, prop.getType() ); + } + + { + Property prop = pc.getProperty( "materializedNclobAtt" ); + assertSame( MaterializedNClobType.INSTANCE, prop.getType() ); + } + + { + Property prop = pc.getProperty( "nclobAtt" ); + assertSame( NClobType.INSTANCE, prop.getType() ); + } + + { + Property prop = pc.getProperty( "nlongvarcharcharAtt" ); + assertSame( NTextType.INSTANCE, prop.getType() ); + } + + { + Property prop = pc.getProperty( "ncharArrAtt" ); + assertSame( StringNVarcharType.INSTANCE, prop.getType() ); + } + + { + Property prop = pc.getProperty( "ncharacterAtt" ); + assertSame( CharacterNCharType.INSTANCE, prop.getType() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/querycache/AbstractQueryCacheResultTransformerTest.java b/hibernate-core/src/test/java/org/hibernate/test/querycache/AbstractQueryCacheResultTransformerTest.java index a72b797a57..ad2645c054 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/querycache/AbstractQueryCacheResultTransformerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/querycache/AbstractQueryCacheResultTransformerTest.java @@ -361,8 +361,8 @@ public abstract class AbstractQueryCacheResultTransformerTest extends BaseCoreFu public void check(Object results) { assertTrue( results instanceof Course ); assertEquals( courseExpected, results ); - assertTrue( Hibernate.isInitialized( ((Course) courseExpected).getCourseMeetings() ) ); - assertEquals( courseExpected.getCourseMeetings(), ((Course) courseExpected).getCourseMeetings() ); + assertTrue( Hibernate.isInitialized( courseExpected.getCourseMeetings() ) ); + assertEquals( courseExpected.getCourseMeetings(), courseExpected.getCourseMeetings() ); } }; runTest( hqlExecutor, criteriaExecutor, checker, true ); @@ -2219,7 +2219,7 @@ public abstract class AbstractQueryCacheResultTransformerTest extends BaseCoreFu } private Constructor getConstructor() { Type studentNametype = - ( ( SessionFactoryImpl ) sessionFactory() ) + sessionFactory() .getEntityPersister( Student.class.getName() ) .getPropertyType( "name" ); return ReflectHelper.getConstructor( Student.class, new Type[] {StandardBasicTypes.LONG, studentNametype} ); @@ -2705,7 +2705,7 @@ public abstract class AbstractQueryCacheResultTransformerTest extends BaseCoreFu } protected void assertCount(int expected) { - int actual = ( int ) sessionFactory().getStatistics().getQueries().length; + int actual = sessionFactory().getStatistics().getQueries().length; assertEquals( expected, actual ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/rowid/RowIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/rowid/RowIdTest.java old mode 100755 new mode 100644 index c90b2317f7..a85a0f3ad5 --- a/hibernate-core/src/test/java/org/hibernate/test/rowid/RowIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/rowid/RowIdTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.dialect.Oracle9iDialect; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -56,19 +57,19 @@ public class RowIdTest extends BaseCoreFunctionalTestCase { public void afterSessionFactoryBuilt() { super.afterSessionFactoryBuilt(); - Session session = sessionFactory().openSession(); + final Session session = sessionFactory().openSession(); session.doWork( new Work() { @Override public void execute(Connection connection) throws SQLException { - Statement st = connection.createStatement(); + Statement st = ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement(); try { - st.execute( "drop table Point"); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().execute( st, "drop table Point"); } catch (Exception ignored) { } - st.execute("create table Point (\"x\" number(19,2) not null, \"y\" number(19,2) not null, description varchar2(255) )"); - st.close(); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().execute( st, "create table Point (\"x\" number(19,2) not null, \"y\" number(19,2) not null, description varchar2(255) )"); + ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().release( st ); } } ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java index 531ebe00cc..9a99586576 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java @@ -23,6 +23,8 @@ */ package org.hibernate.test.sql.autodiscovery; +import static org.junit.Assert.assertEquals; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -30,15 +32,13 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.List; -import org.junit.Assert; -import org.junit.Test; - import org.hibernate.Session; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.loader.custom.NonUniqueDiscoveredSqlAliasException; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; +import org.junit.Assert; +import org.junit.Test; /** * @author Steve Ebersole @@ -121,13 +121,14 @@ public class AutoDiscoveryTest extends BaseCoreFunctionalTestCase { @Test public void testDialectGetColumnAliasExtractor() throws Exception { Session session = openSession(); + final SessionImplementor sessionImplementor = (SessionImplementor) session; session.beginTransaction(); session.doWork( new Work() { @Override public void execute(Connection connection) throws SQLException { - PreparedStatement ps = connection.prepareStatement( QUERY_STRING ); - ResultSet rs = ps.executeQuery(); + PreparedStatement ps = sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( QUERY_STRING ); + ResultSet rs = sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( ps ); try { ResultSetMetaData metadata = rs.getMetaData(); String column1Alias = getDialect().getColumnAliasExtractor().extractColumnAlias( metadata, 1 ); @@ -135,8 +136,8 @@ public class AutoDiscoveryTest extends BaseCoreFunctionalTestCase { Assert.assertFalse( "bad dialect.getColumnAliasExtractor impl", column1Alias.equals( column2Alias ) ); } finally { - rs.close(); - ps.close(); + sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().release( rs ); + sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().release( ps ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/CursorFromCallableTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/CursorFromCallableTest.java new file mode 100644 index 0000000000..966efab8e3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/CursorFromCallableTest.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.sql.refcursor; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Arrays; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.Session; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.jdbc.spi.ResultSetReturn; +import org.hibernate.engine.jdbc.spi.StatementPreparer; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.jdbc.Work; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@RequiresDialect( Oracle8iDialect.class ) +public class CursorFromCallableTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { NumValue.class }; + } + + @Before + public void createRefCursorFunction() { + executeStatement( "CREATE OR REPLACE FUNCTION f_test_return_cursor RETURN SYS_REFCURSOR " + + "IS " + + " l_Cursor SYS_REFCURSOR; " + + "BEGIN " + + " OPEN l_Cursor FOR " + + " SELECT 1 AS BOT_NUM " + + " , 'Line 1' AS BOT_VALUE " + + " FROM DUAL " + + " UNION " + + " SELECT 2 AS BOT_NUM " + + " , 'Line 2' AS BOT_VALUE " + + " FROM DUAL; " + + " RETURN(l_Cursor); " + + "END f_test_return_cursor;" ); + } + + @After + public void dropRefCursorFunction() { + executeStatement( "DROP FUNCTION f_test_return_cursor" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-8022" ) + public void testReadResultSetFromRefCursor() { + Session session = openSession(); + session.getTransaction().begin(); + + Assert.assertEquals( + Arrays.asList( new NumValue( 1, "Line 1" ), new NumValue( 2, "Line 2" ) ), + session.getNamedQuery( "NumValue.getSomeValues" ).list() + ); + + session.getTransaction().commit(); + session.close(); + } + + private void executeStatement(final String sql) { + final Session session = openSession(); + session.getTransaction().begin(); + + session.doWork( new Work() { + @Override + public void execute(Connection connection) throws SQLException { + final JdbcCoordinator jdbcCoordinator = ( (SessionImplementor) session ).getTransactionCoordinator().getJdbcCoordinator(); + final StatementPreparer statementPreparer = jdbcCoordinator.getStatementPreparer(); + final ResultSetReturn resultSetReturn = jdbcCoordinator.getResultSetReturn(); + PreparedStatement preparedStatement = null; + try { + preparedStatement = statementPreparer.prepareStatement( sql ); + resultSetReturn.execute( preparedStatement ); + } + finally { + if ( preparedStatement != null ) { + try { + jdbcCoordinator.release( preparedStatement ); + } + catch ( Throwable ignore ) { + // ignore... + } + } + } + } + } ); + + session.getTransaction().commit(); + session.close(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/NumValue.java b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/NumValue.java new file mode 100644 index 0000000000..2f73207acb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/NumValue.java @@ -0,0 +1,95 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.test.sql.refcursor; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedNativeQueries; +import javax.persistence.NamedNativeQuery; +import javax.persistence.QueryHint; +import javax.persistence.Table; + +@Entity +@Table(name = "BOT_NUMVALUE") +@NamedNativeQueries({ + @NamedNativeQuery(name = "NumValue.getSomeValues", + query = "{ ? = call f_test_return_cursor() }", + resultClass = NumValue.class, hints = { @QueryHint(name = "org.hibernate.callable", value = "true") }) +}) +public class NumValue implements Serializable { + @Id + @Column(name = "BOT_NUM", nullable = false) + private long num; + + @Column(name = "BOT_VALUE") + private String value; + + public NumValue() { + } + + public NumValue(long num, String value) { + this.num = num; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof NumValue ) ) return false; + + NumValue numValue = (NumValue) o; + + if ( num != numValue.num ) return false; + if ( value != null ? !value.equals( numValue.value ) : numValue.value != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) ( num ^ ( num >>> 32 ) ); + result = 31 * result + ( value != null ? value.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "NumValue(num = " + num + ", value = " + value + ")"; + } + + public long getNum() { + return num; + } + + public void setNum(long num) { + this.num = num; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java index 8bc9820fb4..c2c00a7870 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java @@ -26,11 +26,19 @@ package org.hibernate.test.sql.storedproc; import org.junit.Test; import org.hibernate.Session; -import org.hibernate.StoredProcedureCall; -import org.hibernate.StoredProcedureOutputs; -import org.hibernate.StoredProcedureResultSetReturn; -import org.hibernate.StoredProcedureReturn; import org.hibernate.dialect.H2Dialect; +import javax.persistence.ParameterMode; +import java.util.List; + +import org.hibernate.JDBCException; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.Mapping; +import org.hibernate.mapping.AuxiliaryDatabaseObject; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.ProcedureResult; +import org.hibernate.result.ResultSetReturn; +import org.hibernate.result.Return; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.ExtraAssertions; @@ -38,67 +46,289 @@ import org.hibernate.testing.junit4.ExtraAssertions; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Steve Ebersole */ @RequiresDialect( H2Dialect.class ) public class StoredProcedureTest extends BaseCoreFunctionalTestCase { -// this is not working in H2 -// @Override -// protected void configure(Configuration configuration) { -// super.configure( configuration ); -// configuration.addAuxiliaryDatabaseObject( -// new AuxiliaryDatabaseObject() { -// @Override -// public void addDialectScope(String dialectName) { -// } -// -// @Override -// public boolean appliesToDialect(Dialect dialect) { -// return H2Dialect.class.isInstance( dialect ); -// } -// -// @Override -// public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { -// return "CREATE ALIAS findUser AS $$\n" + -// "import org.h2.tools.SimpleResultSet;\n" + -// "import java.sql.*;\n" + -// "@CODE\n" + -// "ResultSet findUser() {\n" + -// " SimpleResultSet rs = new SimpleResultSet();\n" + -// " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + -// " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + -// " rs.addRow(1, \"Steve\");\n" + -// " return rs;\n" + -// "}\n" + -// "$$"; -// } -// -// @Override -// public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { -// return "DROP ALIAS findUser IF EXISTS"; -// } -// } -// ); -// } + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.addAuxiliaryDatabaseObject( + new AuxiliaryDatabaseObject() { + @Override + public void addDialectScope(String dialectName) { + } + + @Override + public boolean appliesToDialect(Dialect dialect) { + return H2Dialect.class.isInstance( dialect ); + } + + @Override + public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { + return "CREATE ALIAS findOneUser AS $$\n" + + "import org.h2.tools.SimpleResultSet;\n" + + "import java.sql.*;\n" + + "@CODE\n" + + "ResultSet findOneUser() {\n" + + " SimpleResultSet rs = new SimpleResultSet();\n" + + " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + + " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + + " rs.addRow(1, \"Steve\");\n" + + " return rs;\n" + + "}\n" + + "$$"; + } + + @Override + public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { + return "DROP ALIAS findUser IF EXISTS"; + } + } + ); + + configuration.addAuxiliaryDatabaseObject( + new AuxiliaryDatabaseObject() { + @Override + public void addDialectScope(String dialectName) { + } + + @Override + public boolean appliesToDialect(Dialect dialect) { + return H2Dialect.class.isInstance( dialect ); + } + + @Override + public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { + return "CREATE ALIAS findUsers AS $$\n" + + "import org.h2.tools.SimpleResultSet;\n" + + "import java.sql.*;\n" + + "@CODE\n" + + "ResultSet findUsers() {\n" + + " SimpleResultSet rs = new SimpleResultSet();\n" + + " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + + " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + + " rs.addRow(1, \"Steve\");\n" + + " rs.addRow(2, \"John\");\n" + + " rs.addRow(3, \"Jane\");\n" + + " return rs;\n" + + "}\n" + + "$$"; + } + + @Override + public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { + return "DROP ALIAS findUser IF EXISTS"; + } + } + ); + + configuration.addAuxiliaryDatabaseObject( + new AuxiliaryDatabaseObject() { + @Override + public void addDialectScope(String dialectName) { + } + + @Override + public boolean appliesToDialect(Dialect dialect) { + return H2Dialect.class.isInstance( dialect ); + } + + @Override + public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { + return "CREATE ALIAS findUserRange AS $$\n" + + "import org.h2.tools.SimpleResultSet;\n" + + "import java.sql.*;\n" + + "@CODE\n" + + "ResultSet findUserRange(int start, int end) {\n" + + " SimpleResultSet rs = new SimpleResultSet();\n" + + " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + + " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + + " for ( int i = start; i < end; i++ ) {\n" + + " rs.addRow(1, \"User \" + i );\n" + + " }\n" + + " return rs;\n" + + "}\n" + + "$$"; + } + + @Override + public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { + return "DROP ALIAS findUser IF EXISTS"; + } + } + ); + } @Test public void baseTest() { Session session = openSession(); session.beginTransaction(); - StoredProcedureCall query = session.createStoredProcedureCall( "user"); - StoredProcedureOutputs outputs = query.getOutputs(); - assertTrue( "Checking StoredProcedureOutputs has more returns", outputs.hasMoreReturns() ); - StoredProcedureReturn nextReturn = outputs.getNextReturn(); + ProcedureCall query = session.createStoredProcedureCall( "user"); + ProcedureResult procedureResult = query.getResult(); + assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() ); + Return nextReturn = procedureResult.getNextReturn(); assertNotNull( nextReturn ); - ExtraAssertions.assertClassAssignability( StoredProcedureResultSetReturn.class, nextReturn.getClass() ); - StoredProcedureResultSetReturn resultSetReturn = (StoredProcedureResultSetReturn) nextReturn; + ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() ); + ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn; String name = (String) resultSetReturn.getSingleResult(); assertEquals( "SA", name ); session.getTransaction().commit(); session.close(); } + + @Test + public void testGetSingleResultTuple() { + Session session = openSession(); + session.beginTransaction(); + + ProcedureCall query = session.createStoredProcedureCall( "findOneUser" ); + ProcedureResult procedureResult = query.getResult(); + assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() ); + Return nextReturn = procedureResult.getNextReturn(); + assertNotNull( nextReturn ); + ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() ); + ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn; + Object result = resultSetReturn.getSingleResult(); + ExtraAssertions.assertTyping( Object[].class, result ); + String name = (String) ( (Object[]) result )[1]; + assertEquals( "Steve", name ); + + session.getTransaction().commit(); + session.close(); + } + + @Test + public void testGetResultListTuple() { + Session session = openSession(); + session.beginTransaction(); + + ProcedureCall query = session.createStoredProcedureCall( "findUsers" ); + ProcedureResult procedureResult = query.getResult(); + assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() ); + Return nextReturn = procedureResult.getNextReturn(); + assertNotNull( nextReturn ); + ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() ); + ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn; + List results = resultSetReturn.getResultList(); + assertEquals( 3, results.size() ); + + for ( Object result : results ) { + ExtraAssertions.assertTyping( Object[].class, result ); + Integer id = (Integer) ( (Object[]) result )[0]; + String name = (String) ( (Object[]) result )[1]; + if ( id.equals( 1 ) ) { + assertEquals( "Steve", name ); + } + else if ( id.equals( 2 ) ) { + assertEquals( "John", name ); + } + else if ( id.equals( 3 ) ) { + assertEquals( "Jane", name ); + } + else { + fail( "Unexpected id value found [" + id + "]" ); + } + } + + session.getTransaction().commit(); + session.close(); + } + + @Test + public void testInParametersByName() { + Session session = openSession(); + session.beginTransaction(); + + ProcedureCall query = session.createStoredProcedureCall( "findUserRange" ); + query.registerParameter( "start", Integer.class, ParameterMode.IN ).bindValue( 1 ); + query.registerParameter( "end", Integer.class, ParameterMode.IN ).bindValue( 2 ); + ProcedureResult procedureResult = query.getResult(); + assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() ); + Return nextReturn = procedureResult.getNextReturn(); + assertNotNull( nextReturn ); + ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() ); + ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn; + List results = resultSetReturn.getResultList(); + assertEquals( 1, results.size() ); + Object result = results.get( 0 ); + ExtraAssertions.assertTyping( Object[].class, result ); + Integer id = (Integer) ( (Object[]) result )[0]; + String name = (String) ( (Object[]) result )[1]; + assertEquals( 1, (int) id ); + assertEquals( "User 1", name ); + + session.getTransaction().commit(); + session.close(); + } + + @Test + public void testInParametersByPosition() { + Session session = openSession(); + session.beginTransaction(); + + ProcedureCall query = session.createStoredProcedureCall( "findUserRange" ); + query.registerParameter( 1, Integer.class, ParameterMode.IN ).bindValue( 1 ); + query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 ); + ProcedureResult procedureResult = query.getResult(); + assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() ); + Return nextReturn = procedureResult.getNextReturn(); + assertNotNull( nextReturn ); + ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() ); + ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn; + List results = resultSetReturn.getResultList(); + assertEquals( 1, results.size() ); + Object result = results.get( 0 ); + ExtraAssertions.assertTyping( Object[].class, result ); + Integer id = (Integer) ( (Object[]) result )[0]; + String name = (String) ( (Object[]) result )[1]; + assertEquals( 1, (int) id ); + assertEquals( "User 1", name ); + + session.getTransaction().commit(); + session.close(); + } + + @Test + public void testInParametersNotSet() { + Session session = openSession(); + session.beginTransaction(); + + // since the procedure does not define defaults for parameters this should result in SQLExceptions on + // execution + + { + ProcedureCall query = session.createStoredProcedureCall( "findUserRange" ); + query.registerParameter( 1, Integer.class, ParameterMode.IN ); + query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 ); + ProcedureResult procedureResult = query.getResult(); + try { + procedureResult.hasMoreReturns(); + fail( "Expecting failure due to missing parameter bind" ); + } + catch (JDBCException expected) { + } + } + + { + ProcedureCall query = session.createStoredProcedureCall( "findUserRange" ); + query.registerParameter( "start", Integer.class, ParameterMode.IN ); + query.registerParameter( "end", Integer.class, ParameterMode.IN ).bindValue( 2 ); + ProcedureResult procedureResult = query.getResult(); + try { + procedureResult.hasMoreReturns(); + fail( "Expecting failure due to missing parameter bind" ); + } + catch (JDBCException expected) { + } + } + + session.getTransaction().commit(); + session.close(); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/tm/TransactionTimeoutTest.java b/hibernate-core/src/test/java/org/hibernate/test/tm/TransactionTimeoutTest.java index 666a71a508..22f90f31ce 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/tm/TransactionTimeoutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/tm/TransactionTimeoutTest.java @@ -77,7 +77,7 @@ public class TransactionTimeoutTest extends BaseCoreFunctionalTestCase { public void testTransactionTimeoutSuccess() { Session session = openSession(); Transaction transaction = session.getTransaction(); - transaction.setTimeout( 5 ); + transaction.setTimeout( 60 ); transaction.begin(); session.persist( new Person( "Lukasz", "Antoniak" ) ); transaction.commit(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/transaction/jdbc/TestExpectedUsage.java b/hibernate-core/src/test/java/org/hibernate/test/transaction/jdbc/TestExpectedUsage.java index 95988c4691..42a4f7e03b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/transaction/jdbc/TestExpectedUsage.java +++ b/hibernate-core/src/test/java/org/hibernate/test/transaction/jdbc/TestExpectedUsage.java @@ -23,18 +23,19 @@ */ package org.hibernate.test.transaction.jdbc; -import java.sql.Connection; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import org.hibernate.ConnectionReleaseMode; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl; import org.hibernate.engine.transaction.spi.TransactionContext; @@ -44,11 +45,9 @@ import org.hibernate.test.common.TransactionContextImpl; import org.hibernate.test.common.TransactionEnvironmentImpl; import org.hibernate.testing.env.ConnectionProviderBuilder; import org.hibernate.testing.junit4.BaseUnitTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; /** * @author Steve Ebersole @@ -81,49 +80,45 @@ public class TestExpectedUsage extends BaseUnitTestCase { JournalingTransactionObserver observer = new JournalingTransactionObserver(); transactionCoordinator.addObserver( observer ); - LogicalConnectionImplementor logicalConnection = transactionCoordinator.getJdbcCoordinator().getLogicalConnection(); - Connection connection = logicalConnection.getShareableConnectionProxy(); + JdbcCoordinator jdbcCoordinator = transactionCoordinator.getJdbcCoordinator(); + LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection(); // set up some tables to use - try { - Statement statement = connection.createStatement(); - statement.execute( "drop table SANDBOX_JDBC_TST if exists" ); - statement.execute( "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertTrue( logicalConnection.isPhysicallyConnected() ); - statement.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertTrue( logicalConnection.isPhysicallyConnected() ); // after_transaction specified - } - catch ( SQLException sqle ) { - fail( "incorrect exception type : SQLException" ); - } + Statement statement = jdbcCoordinator.getStatementPreparer().createStatement(); + jdbcCoordinator.getResultSetReturn().execute( statement, "drop table SANDBOX_JDBC_TST if exists" ); + jdbcCoordinator.getResultSetReturn().execute( statement, "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); + assertTrue( logicalConnection.isPhysicallyConnected() ); + jdbcCoordinator.release( statement ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); + assertTrue( logicalConnection.isPhysicallyConnected() ); // after_transaction specified // ok, now we can get down to it... TransactionImplementor txn = transactionCoordinator.getTransaction(); // same as Session#getTransaction txn.begin(); assertEquals( 1, observer.getBegins() ); try { - PreparedStatement ps = connection.prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); + PreparedStatement ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); ps.setLong( 1, 1 ); ps.setString( 2, "name" ); - ps.execute(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - ps.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoordinator.getResultSetReturn().execute( ps ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); + jdbcCoordinator.release( ps ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); - ps = connection.prepareStatement( "select * from SANDBOX_JDBC_TST" ); - ps.executeQuery(); - connection.prepareStatement( "delete from SANDBOX_JDBC_TST" ).execute(); + ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "select * from SANDBOX_JDBC_TST" ); + jdbcCoordinator.getResultSetReturn().extract( ps ); + ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "delete from SANDBOX_JDBC_TST" ); + jdbcCoordinator.getResultSetReturn().execute( ps ); // lets forget to close these... - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); // and commit the transaction... txn.commit(); // we should now have: // 1) no resources because of after_transaction release mode - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); // 2) non-physically connected logical connection, again because of after_transaction release mode assertFalse( logicalConnection.isPhysicallyConnected() ); // 3) transaction observer callbacks diff --git a/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/BasicDrivingTest.java b/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/BasicDrivingTest.java index 4552e03eef..386b98e42f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/BasicDrivingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/BasicDrivingTest.java @@ -23,20 +23,21 @@ */ package org.hibernate.test.transaction.jta; -import java.sql.Connection; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.cfg.Environment; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl; import org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory; @@ -49,11 +50,9 @@ import org.hibernate.test.common.TransactionEnvironmentImpl; import org.hibernate.testing.env.ConnectionProviderBuilder; import org.hibernate.testing.jta.TestingJtaBootstrap; import org.hibernate.testing.junit4.BaseUnitTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; /** * Testing transaction handling when the JTA transaction facade is the driver. @@ -88,23 +87,18 @@ public class BasicDrivingTest extends BaseUnitTestCase { JournalingTransactionObserver observer = new JournalingTransactionObserver(); transactionCoordinator.addObserver( observer ); - LogicalConnectionImplementor logicalConnection = transactionCoordinator.getJdbcCoordinator().getLogicalConnection(); - Connection connection = logicalConnection.getShareableConnectionProxy(); + JdbcCoordinator jdbcCoordinator = transactionCoordinator.getJdbcCoordinator(); + LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection(); // set up some tables to use - try { - Statement statement = connection.createStatement(); - statement.execute( "drop table SANDBOX_JDBC_TST if exists" ); - statement.execute( "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertTrue( logicalConnection.isPhysicallyConnected() ); - statement.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertFalse( logicalConnection.isPhysicallyConnected() ); // after_statement specified - } - catch ( SQLException sqle ) { - fail( "incorrect exception type : SQLException" ); - } + Statement statement = jdbcCoordinator.getStatementPreparer().createStatement(); + jdbcCoordinator.getResultSetReturn().execute( statement, "drop table SANDBOX_JDBC_TST if exists" ); + jdbcCoordinator.getResultSetReturn().execute( statement, "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); + assertTrue( logicalConnection.isPhysicallyConnected() ); + jdbcCoordinator.release( statement ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); + assertFalse( logicalConnection.isPhysicallyConnected() ); // after_statement specified // ok, now we can get down to it... TransactionImplementor txn = transactionCoordinator.getTransaction(); // same as Session#getTransaction @@ -112,19 +106,20 @@ public class BasicDrivingTest extends BaseUnitTestCase { assertEquals( 1, observer.getBegins() ); assertTrue( txn.isInitiator() ); try { - PreparedStatement ps = connection.prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); + PreparedStatement ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); ps.setLong( 1, 1 ); ps.setString( 2, "name" ); - ps.execute(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - ps.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoordinator.getResultSetReturn().execute( ps ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); + jdbcCoordinator.release( ps ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); - ps = connection.prepareStatement( "select * from SANDBOX_JDBC_TST" ); - ps.executeQuery(); - connection.prepareStatement( "delete from SANDBOX_JDBC_TST" ).execute(); + ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "select * from SANDBOX_JDBC_TST" ); + jdbcCoordinator.getResultSetReturn().extract( ps ); + ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "delete from SANDBOX_JDBC_TST" ); + jdbcCoordinator.getResultSetReturn().execute( ps ); // lets forget to close these... - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); assertTrue( logicalConnection.isPhysicallyConnected() ); // and commit the transaction... @@ -132,7 +127,7 @@ public class BasicDrivingTest extends BaseUnitTestCase { // we should now have: // 1) no resources because of after_transaction release mode - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); // 2) non-physically connected logical connection, again because of after_transaction release mode assertFalse( logicalConnection.isPhysicallyConnected() ); // 3) transaction observer callbacks diff --git a/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/ManagedDrivingTest.java b/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/ManagedDrivingTest.java index 7e32644ce7..ec841c4ebb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/ManagedDrivingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/transaction/jta/ManagedDrivingTest.java @@ -23,7 +23,11 @@ */ package org.hibernate.test.transaction.jta; -import java.sql.Connection; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; @@ -40,6 +44,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.cfg.Environment; import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl; import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory; @@ -53,11 +58,6 @@ import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.jta.TestingJtaBootstrap; import org.hibernate.testing.junit4.BaseUnitTestCase; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * Testing transaction facade handling when the transaction is being driven by something other than the facade. * @@ -97,23 +97,18 @@ public class ManagedDrivingTest extends BaseUnitTestCase { final JournalingTransactionObserver transactionObserver = new JournalingTransactionObserver(); transactionCoordinator.addObserver( transactionObserver ); - final LogicalConnectionImplementor logicalConnection = transactionCoordinator.getJdbcCoordinator().getLogicalConnection(); - Connection connection = logicalConnection.getShareableConnectionProxy(); + JdbcCoordinator jdbcCoordinator = transactionCoordinator.getJdbcCoordinator(); + LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection(); // set up some tables to use - try { - Statement statement = connection.createStatement(); - statement.execute( "drop table SANDBOX_JDBC_TST if exists" ); - statement.execute( "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertTrue( logicalConnection.isPhysicallyConnected() ); - statement.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - assertFalse( logicalConnection.isPhysicallyConnected() ); // after_statement specified - } - catch ( SQLException sqle ) { - fail( "incorrect exception type : SQLException" ); - } + Statement statement = jdbcCoordinator.getStatementPreparer().createStatement(); + jdbcCoordinator.getResultSetReturn().execute( statement, "drop table SANDBOX_JDBC_TST if exists" ); + jdbcCoordinator.getResultSetReturn().execute( statement, "create table SANDBOX_JDBC_TST ( ID integer, NAME varchar(100) )" ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); + assertTrue( logicalConnection.isPhysicallyConnected() ); + jdbcCoordinator.release( statement ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); + assertFalse( logicalConnection.isPhysicallyConnected() ); // after_statement specified JtaPlatform instance = serviceRegistry.getService( JtaPlatform.class ); TransactionManager transactionManager = instance.retrieveTransactionManager(); @@ -126,34 +121,34 @@ public class ManagedDrivingTest extends BaseUnitTestCase { txn.begin(); assertEquals( 1, transactionObserver.getBegins() ); assertFalse( txn.isInitiator() ); - connection = logicalConnection.getShareableConnectionProxy(); try { - PreparedStatement ps = connection.prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); + PreparedStatement ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" ); ps.setLong( 1, 1 ); ps.setString( 2, "name" ); - ps.execute(); - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); - ps.close(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + jdbcCoordinator.getResultSetReturn().execute( ps ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); + jdbcCoordinator.release( ps ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); - ps = connection.prepareStatement( "select * from SANDBOX_JDBC_TST" ); - ps.executeQuery(); - connection.prepareStatement( "delete from SANDBOX_JDBC_TST" ).execute(); + ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "select * from SANDBOX_JDBC_TST" ); + jdbcCoordinator.getResultSetReturn().extract( ps ); + ps = jdbcCoordinator.getStatementPreparer().prepareStatement( "delete from SANDBOX_JDBC_TST" ); + jdbcCoordinator.getResultSetReturn().execute( ps ); // lets forget to close these... - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); assertTrue( logicalConnection.isPhysicallyConnected() ); // and commit the transaction... txn.commit(); // since txn is not a driver, nothing should have changed... - assertTrue( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertTrue( jdbcCoordinator.hasRegisteredResources() ); assertTrue( logicalConnection.isPhysicallyConnected() ); assertEquals( 0, transactionObserver.getBeforeCompletions() ); assertEquals( 0, transactionObserver.getAfterCompletions() ); transactionManager.commit(); - assertFalse( logicalConnection.getResourceRegistry().hasRegisteredResources() ); + assertFalse( jdbcCoordinator.hasRegisteredResources() ); assertFalse( logicalConnection.isPhysicallyConnected() ); assertEquals( 1, transactionObserver.getBeforeCompletions() ); assertEquals( 1, transactionObserver.getAfterCompletions() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeparameters/TypeParameterTest.java b/hibernate-core/src/test/java/org/hibernate/test/typeparameters/TypeParameterTest.java index 9fd9e24aad..6de66a4cb9 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/typeparameters/TypeParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/typeparameters/TypeParameterTest.java @@ -23,20 +23,20 @@ */ package org.hibernate.test.typeparameters; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import org.junit.Test; - import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Test; /** * Test for parameterizable types. @@ -68,13 +68,22 @@ public class TypeParameterTest extends BaseCoreFunctionalTestCase { s = openSession(); s.beginTransaction(); + doWork(id, s); + + s.getTransaction().commit(); + s.close(); + + deleteData(); + } + + private void doWork(final Integer id, final Session s) { s.doWork( new Work() { @Override public void execute(Connection connection) throws SQLException { - PreparedStatement statement = connection.prepareStatement("SELECT * FROM STRANGE_TYPED_OBJECT WHERE ID=?"); + PreparedStatement statement = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( "SELECT * FROM STRANGE_TYPED_OBJECT WHERE ID=?" ); statement.setInt(1, id.intValue()); - ResultSet resultSet = statement.executeQuery(); + ResultSet resultSet = ((SessionImplementor)s).getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().extract( statement ); assertTrue("A row should have been returned", resultSet.next()); assertTrue("Default value should have been mapped to null", resultSet.getObject("VALUE_ONE") == null); @@ -84,11 +93,6 @@ public class TypeParameterTest extends BaseCoreFunctionalTestCase { } } ); - - s.getTransaction().commit(); - s.close(); - - deleteData(); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/SchemaUtil.java b/hibernate-core/src/test/java/org/hibernate/test/util/SchemaUtil.java index 76cbe8c5bc..ac8c9938e1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/util/SchemaUtil.java +++ b/hibernate-core/src/test/java/org/hibernate/test/util/SchemaUtil.java @@ -44,7 +44,7 @@ import org.hibernate.metamodel.spi.relational.TableSpecification; * @author Brett Meyer */ public abstract class SchemaUtil { - + public static boolean isColumnPresent( String tableName, String columnName, MetadataImplementor metadata ) { try { @@ -92,7 +92,7 @@ public abstract class SchemaUtil { } throw new AssertionFailure( "can't find table " +tableName ); } - + public static TableSpecification getTable( String tableName, MetadataImplementor metadata ) throws AssertionFailure { return getTable( null, tableName, metadata ); diff --git a/hibernate-core/src/test/resources/hibernate.properties b/hibernate-core/src/test/resources/hibernate.properties index 9f6aad34eb..cae1c6c221 100644 --- a/hibernate-core/src/test/resources/hibernate.properties +++ b/hibernate-core/src/test/resources/hibernate.properties @@ -30,6 +30,7 @@ hibernate.connection.pool_size 5 hibernate.show_sql false hibernate.format_sql true + hibernate.max_fetch_depth 5 hibernate.cache.region_prefix hibernate.test diff --git a/hibernate-core/src/test/resources/log4j.properties b/hibernate-core/src/test/resources/log4j.properties deleted file mode 100644 index 686aae8fc2..0000000000 --- a/hibernate-core/src/test/resources/log4j.properties +++ /dev/null @@ -1,17 +0,0 @@ -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n - - -log4j.rootLogger=info, stdout - -log4j.logger.org.hibernate.tool.hbm2ddl=trace -log4j.logger.org.hibernate.testing.cache=debug - -# SQL Logging - HHH-6833 -log4j.logger.org.hibernate.SQL=debug - -log4j.logger.org.hibernate.hql.internal.ast=debug - -log4j.logger.org.hibernate.sql.ordering.antlr=debug \ No newline at end of file diff --git a/hibernate-core/src/test/resources/org/hibernate/test/collection/list/Mappings.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/collection/list/Mappings.hbm.xml index 2832963db2..eafdc007ab 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/collection/list/Mappings.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/collection/list/Mappings.hbm.xml @@ -1,13 +1,13 @@ + "-//Hibernate/Hibernate Mapping DTD 3.0//EN" + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + @@ -16,6 +16,27 @@ - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hibernate-core/src/test/resources/org/hibernate/test/event/collection/detached/MultipleCollectionBagMapping.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/event/collection/detached/MultipleCollectionBagMapping.hbm.xml index 3426406f49..0c49cfb074 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/event/collection/detached/MultipleCollectionBagMapping.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/event/collection/detached/MultipleCollectionBagMapping.hbm.xml @@ -33,7 +33,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/hibernate-ehcache/hibernate-ehcache.gradle b/hibernate-ehcache/hibernate-ehcache.gradle index 24b887f9d7..0f867673eb 100644 --- a/hibernate-ehcache/hibernate-ehcache.gradle +++ b/hibernate-ehcache/hibernate-ehcache.gradle @@ -4,3 +4,10 @@ dependencies { testCompile project( ':hibernate-testing' ) } + +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM EHCache' + instruction 'Bundle-SymbolicName', 'org.hibernate.ehcache' + } +} \ No newline at end of file diff --git a/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/management/impl/EhcacheHibernateMBeanRegistrationImpl.java b/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/management/impl/EhcacheHibernateMBeanRegistrationImpl.java index 6083c6e4b0..9605094fb6 100644 --- a/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/management/impl/EhcacheHibernateMBeanRegistrationImpl.java +++ b/hibernate-ehcache/src/main/java/org/hibernate/cache/ehcache/management/impl/EhcacheHibernateMBeanRegistrationImpl.java @@ -112,7 +112,7 @@ public class EhcacheHibernateMBeanRegistrationImpl exception ); } - status = status.STATUS_ALIVE; + status = Status.STATUS_ALIVE; } private MBeanServer getMBeanServer() { diff --git a/hibernate-entitymanager/hibernate-entitymanager.gradle b/hibernate-entitymanager/hibernate-entitymanager.gradle index 617ce7a0d3..7266a4f652 100644 --- a/hibernate-entitymanager/hibernate-entitymanager.gradle +++ b/hibernate-entitymanager/hibernate-entitymanager.gradle @@ -21,6 +21,25 @@ dependencies { testRuntime( "org.jboss.ejb3:jboss-ejb3-api:3.1.0" ) } +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM JPA Entity Manager' + instruction 'Bundle-SymbolicName', 'org.hibernate.entitymanager' + + // A cdi-api OSGi bundle does not currently exist. For now, explicitly + // ignore its packages. This will only cause issues if an app tries + // to use the BeanManagerListenerFactory functionality. + // NOTE: The "!" negates the package, keeping it out of Import-Package + // and including it in Ignore-Package. Also note that '*' does not mean + // * will occur. This is simply a + // BND instruction -- the auto-discovery of imported packages still + // occurs. + instruction 'Import-Package', + '!javax.enterprise*', + '*' + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////////// // JPA model-gen set up //////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java index b027e11be7..fb4a34a7a8 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java @@ -24,6 +24,8 @@ package org.hibernate.jpa; +import org.hibernate.internal.util.StringHelper; + /** * Defines the available HEM settings, both JPA-defined as well as Hibernate-specific *

@@ -192,6 +194,164 @@ public interface AvailableSettings extends org.hibernate.cfg.AvailableSettings { */ public static final String CDI_BEAN_MANAGER = "javax.persistence.bean.manager"; + /** + * Specifies whether schema generation commands for schema creation are to be determine based on object/relational + * mapping metadata, DDL scripts, or a combination of the two. See {@link SchemaGenSource} for valid set of values. + * If no value is specified, a default is assumed as follows:

    + *
  • + * if source scripts are specified (per {@value #SCHEMA_GEN_CREATE_SCRIPT_SOURCE}),then "scripts" is assumed + *
  • + *
  • + * otherwise, "metadata" is assumed + *
  • + *
+ * + * @see SchemaGenSource + */ + public static final String SCHEMA_GEN_CREATE_SOURCE = "javax.persistence.schema-generation.create-source "; + + /** + * Specifies whether schema generation commands for schema dropping are to be determine based on object/relational + * mapping metadata, DDL scripts, or a combination of the two. See {@link SchemaGenSource} for valid set of values. + * If no value is specified, a default is assumed as follows:
    + *
  • + * if source scripts are specified (per {@value #SCHEMA_GEN_DROP_SCRIPT_SOURCE}),then "scripts" is assumed + *
  • + *
  • + * otherwise, "metadata" is assumed + *
  • + *
+ * + * @see SchemaGenSource + */ + public static final String SCHEMA_GEN_DROP_SOURCE = "javax.persistence.schema-generation.drop-source "; + + /** + * Specifies the CREATE script file as either a {@link java.io.Reader} configured for reading of the DDL script + * file or a string designating a file {@link java.net.URL} for the DDL script. + * + * @see #SCHEMA_GEN_CREATE_SOURCE + * @see #SCHEMA_GEN_DROP_SCRIPT_SOURCE + */ + public static final String SCHEMA_GEN_CREATE_SCRIPT_SOURCE = "javax.persistence.schema-generation.create-script-source "; + + /** + * Specifies the DROP script file as either a {@link java.io.Reader} configured for reading of the DDL script + * file or a string designating a file {@link java.net.URL} for the DDL script. + * + * @see #SCHEMA_GEN_DROP_SOURCE + * @see #SCHEMA_GEN_CREATE_SCRIPT_SOURCE + */ + public static final String SCHEMA_GEN_DROP_SCRIPT_SOURCE = "javax.persistence.schema-generation.drop-script-source "; + + /** + * Specifies the type of schema generation action to be taken by the persistence provider in regards to sending + * commands directly to the database via JDBC. See {@link SchemaGenAction} for the set of possible values. + *

+ * If no value is specified, the default is "none". + * + * @see SchemaGenAction + */ + public static final String SCHEMA_GEN_DATABASE_ACTION = "javax.persistence.schema-generation.database.action"; + + /** + * Specifies the type of schema generation action to be taken by the persistence provider in regards to writing + * commands to DDL script files. See {@link SchemaGenAction} for the set of possible values. + *

+ * If no value is specified, the default is "none". + * + * @see SchemaGenAction + */ + public static final String SCHEMA_GEN_SCRIPTS_ACTION = "javax.persistence.schema-generation.scripts.action"; + + /** + * For cases where the {@value #SCHEMA_GEN_SCRIPTS_ACTION} value indicates that schema creation commands should + * be written to DDL script file, {@value #SCHEMA_GEN_SCRIPTS_CREATE_TARGET} specifies either a + * {@link java.io.Writer} configured for output of the DDL script or a string specifying the file URL for the DDL + * script. + * + * @see #SCHEMA_GEN_SCRIPTS_ACTION + */ + @SuppressWarnings("JavaDoc") + public static final String SCHEMA_GEN_SCRIPTS_CREATE_TARGET = "javax.persistence.schema-generation.scripts.create-target"; + + /** + * For cases where the {@value #SCHEMA_GEN_SCRIPTS_ACTION} value indicates that schema drop commands should + * be written to DDL script file, {@value #SCHEMA_GEN_SCRIPTS_DROP_TARGET} specifies either a + * {@link java.io.Writer} configured for output of the DDL script or a string specifying the file URL for the DDL + * script. + * + * @see #SCHEMA_GEN_SCRIPTS_ACTION + */ + @SuppressWarnings("JavaDoc") + public static final String SCHEMA_GEN_SCRIPTS_DROP_TARGET = "javax.persistence.schema-generation.scripts.drop-target"; + + /** + * Specifies whether the persistence provider is to create the database schema(s) in addition to creating + * database objects (tables, sequences, constraints, etc). The value of this boolean property should be set + * to {@code true} if the persistence provider is to create schemas in the database or to generate DDL that + * contains “CREATE SCHEMA” commands. If this property is not supplied (or is explicitly {@code false}), the + * provider should not attempt to create database schemas. + */ + public static final String SCHEMA_GEN_CREATE_SCHEMAS = "javax.persistence.create-database-schemas"; + + /** + * Allows passing the specific {@link java.sql.Connection} instance to be used for performing schema generation + * where the target is "database". + *

+ * May also be used to determine the values for {@value #SCHEMA_GEN_DB_NAME}, + * {@value #SCHEMA_GEN_DB_MAJOR_VERSION} and {@value #SCHEMA_GEN_DB_MINOR_VERSION}. + */ + public static final String SCHEMA_GEN_CONNECTION = "javax.persistence.schema-generation-connection"; + + /** + * Specifies the name of the database provider in cases where a Connection to the underlying database is + * not available (aka, mainly in generating scripts). In such cases, a value for + * {@value #SCHEMA_GEN_DB_NAME} *must* be specified. + *

+ * The value of this setting is expected to match the value returned by + * {@link java.sql.DatabaseMetaData#getDatabaseProductName()} for the target database. + *

+ * Additionally specifying {@value #SCHEMA_GEN_DB_MAJOR_VERSION} and/or {@value #SCHEMA_GEN_DB_MINOR_VERSION} + * may be required to understand exactly how to generate the required schema commands. + * + * @see #SCHEMA_GEN_DB_MAJOR_VERSION + * @see #SCHEMA_GEN_DB_MINOR_VERSION + */ + @SuppressWarnings("JavaDoc") + public static final String SCHEMA_GEN_DB_NAME = "javax.persistence.database-product-name"; + + /** + * Specifies the major version of the underlying database, as would be returned by + * {@link java.sql.DatabaseMetaData#getDatabaseMajorVersion} for the target database. This value is used to + * help more precisely determine how to perform schema generation tasks for the underlying database in cases + * where {@value #SCHEMA_GEN_DB_NAME} does not provide enough distinction. + + * @see #SCHEMA_GEN_DB_NAME + * @see #SCHEMA_GEN_DB_MINOR_VERSION + */ + public static final String SCHEMA_GEN_DB_MAJOR_VERSION = "javax.persistence.database-major-version"; + + /** + * Specifies the minor version of the underlying database, as would be returned by + * {@link java.sql.DatabaseMetaData#getDatabaseMinorVersion} for the target database. This value is used to + * help more precisely determine how to perform schema generation tasks for the underlying database in cases + * where te combination of {@value #SCHEMA_GEN_DB_NAME} and {@value #SCHEMA_GEN_DB_MAJOR_VERSION} does not provide + * enough distinction. + * + * @see #SCHEMA_GEN_DB_NAME + * @see #SCHEMA_GEN_DB_MAJOR_VERSION + */ + public static final String SCHEMA_GEN_DB_MINOR_VERSION = "javax.persistence.database-minor-version"; + + /** + * Specifies a {@link java.io.Reader} configured for reading of the SQL load script or a string designating the + * file {@link java.net.URL} for the SQL load script. + *

+ * A "SQL load script" is a script that performs some database initialization (INSERT, etc). + */ + public static final String SCHEMA_GEN_LOAD_SCRIPT_SOURCE = "javax.persistence.sql-load-script-source"; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hibernate specific settings diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java index 90054fea1f..2a1d80ffae 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java @@ -23,38 +23,47 @@ */ package org.hibernate.jpa; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceException; import javax.persistence.spi.LoadState; import javax.persistence.spi.PersistenceProvider; import javax.persistence.spi.PersistenceUnitInfo; import javax.persistence.spi.ProviderUtil; -import java.util.Collections; -import java.util.List; -import java.util.Map; import org.jboss.logging.Logger; -import org.hibernate.jpa.internal.EntityManagerMessageLogger; -import org.hibernate.jpa.internal.util.PersistenceUtilHelper; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.internal.PersistenceXmlParser; import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.ProviderChecker; +import org.hibernate.jpa.internal.util.PersistenceUtilHelper; /** * The Hibernate {@link PersistenceProvider} implementation * * @author Gavin King * @author Steve Ebersole + * @author Brett Meyer */ public class HibernatePersistenceProvider implements PersistenceProvider { - private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger( - EntityManagerMessageLogger.class, - HibernatePersistenceProvider.class.getName() - ); + private static final Logger log = Logger.getLogger( HibernatePersistenceProvider.class ); private final PersistenceUtilHelper.MetadataCache cache = new PersistenceUtilHelper.MetadataCache(); + + /** + * Used for environment-supplied properties. Ex: hibernate-osgi's + * HibernateBundleActivator needs to set a custom JtaPlatform. + */ + private Map environmentProperties; + + public void setEnvironmentProperties( Map environmentProperties ) { + this.environmentProperties = environmentProperties; + } /** * {@inheritDoc} @@ -63,8 +72,40 @@ public class HibernatePersistenceProvider implements PersistenceProvider { */ @Override public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map properties) { + log.tracef( "Starting createEntityManagerFactory for persistenceUnitName %s", persistenceUnitName ); + + if ( environmentProperties != null ) { + properties.putAll( environmentProperties ); + } + + final EntityManagerFactoryBuilder builder = getEntityManagerFactoryBuilderOrNull( persistenceUnitName, properties ); + if ( builder == null ) { + log.trace( "Could not obtain matching EntityManagerFactoryBuilder, returning null" ); + return null; + } + else { + return builder.build(); + } + } + + private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties) { + log.tracef( "Attempting to obtain correct EntityManagerFactoryBuilder for persistenceUnitName : %s", persistenceUnitName ); + final Map integration = wrap( properties ); - final List units = PersistenceXmlParser.locatePersistenceUnits( integration ); + final List units; + try { + units = PersistenceXmlParser.locatePersistenceUnits( integration ); + } + catch (RuntimeException e) { + log.debug( "Unable to locate persistence units", e ); + throw e; + } + catch (Exception e) { + log.debug( "Unable to locate persistence units", e ); + throw new PersistenceException( "Unable to locate persistence units", e ); + } + + log.debugf( "Located and parsed %s persistence units; checking each", units.size() ); if ( persistenceUnitName == null && units.size() > 1 ) { // no persistence-unit name to look for was given and we found multiple persistence-units @@ -72,25 +113,35 @@ public class HibernatePersistenceProvider implements PersistenceProvider { } for ( ParsedPersistenceXmlDescriptor persistenceUnit : units ) { + log.debugf( + "Checking persistence-unit [name=%s, explicit-provider=%s] against incoming persistence unit name [%s]", + persistenceUnit.getName(), + persistenceUnit.getProviderClassName(), + persistenceUnitName + ); + boolean matches = persistenceUnitName == null || persistenceUnit.getName().equals( persistenceUnitName ); if ( !matches ) { + log.debug( "Excluding from consideration due to name mis-match" ); continue; } // See if we (Hibernate) are the persistence provider if ( ! ProviderChecker.isProvider( persistenceUnit, properties ) ) { + log.debug( "Excluding from consideration due to provider mis-match" ); continue; } - return Bootstrap.getEntityManagerFactoryBuilder( persistenceUnit, integration ).build(); + return Bootstrap.getEntityManagerFactoryBuilder( persistenceUnit, integration ); } + log.debug( "Found no matching persistence units" ); return null; } @SuppressWarnings("unchecked") private static Map wrap(Map properties) { - return properties== null ? Collections.emptyMap() : Collections.unmodifiableMap( properties ); + return properties == null ? Collections.emptyMap() : Collections.unmodifiableMap( properties ); } /** @@ -99,19 +150,46 @@ public class HibernatePersistenceProvider implements PersistenceProvider { * Note: per-spec, the values passed as {@code properties} override values found in {@link PersistenceUnitInfo} */ @Override - public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map integration) { - return Bootstrap.getEntityManagerFactoryBuilder( info, integration ).build(); + public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) { + log.tracef( "Starting createContainerEntityManagerFactory : %s", info.getPersistenceUnitName() ); + + if ( environmentProperties != null ) { + properties.putAll( environmentProperties ); + } + return Bootstrap.getEntityManagerFactoryBuilder( info, properties ).build(); + } + + @Override + public void generateSchema(PersistenceUnitInfo info, Map map) { + log.tracef( "Starting generateSchema : PUI.name=%s", info.getPersistenceUnitName() ); + + EntityManagerFactoryBuilder builder = Bootstrap.getEntityManagerFactoryBuilder( info, map ); + builder.generateSchema(); + } + + @Override + public boolean generateSchema(String persistenceUnitName, Map map) { + log.tracef( "Starting generateSchema for persistenceUnitName %s", persistenceUnitName ); + + final EntityManagerFactoryBuilder builder = getEntityManagerFactoryBuilderOrNull( persistenceUnitName, map ); + if ( builder == null ) { + log.trace( "Could not obtain matching EntityManagerFactoryBuilder, returning false" ); + return false; + } + builder.generateSchema(); + return true; } private final ProviderUtil providerUtil = new ProviderUtil() { + @Override public LoadState isLoadedWithoutReference(Object proxy, String property) { return PersistenceUtilHelper.isLoadedWithoutReference( proxy, property, cache ); } - + @Override public LoadState isLoadedWithReference(Object proxy, String property) { return PersistenceUtilHelper.isLoadedWithReference( proxy, property, cache ); } - + @Override public LoadState isLoaded(Object o) { return PersistenceUtilHelper.isLoaded(o); } @@ -122,4 +200,4 @@ public class HibernatePersistenceProvider implements PersistenceProvider { return providerUtil; } -} \ No newline at end of file +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenAction.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenAction.java new file mode 100644 index 0000000000..2f58ca44da --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenAction.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa; + +import org.hibernate.internal.util.StringHelper; + +/** + * Describes the allowable values of the {@value AvailableSettings#SCHEMA_GEN_DATABASE_ACTION} and + * {@link AvailableSettings#SCHEMA_GEN_SCRIPTS_ACTION} settings. + * + * @see AvailableSettings#SCHEMA_GEN_DATABASE_ACTION + * @see AvailableSettings#SCHEMA_GEN_SCRIPTS_ACTION + * + * @author Steve Ebersole + */ +public enum SchemaGenAction { + /** + * "none" - no actions will be performed (aka, generation is disabled). + */ + NONE( "none" ), + /** + * "create" - database creation will be generated + */ + CREATE( "create" ), + /** + * "drop" - database dropping will be generated + */ + DROP( "drop" ), + /** + * "drop-and-create" - both database creation and database dropping will be generated. + */ + BOTH( "drop-and-create" ); + + private final String externalName; + + private SchemaGenAction(String externalName) { + this.externalName = externalName; + } + + /** + * Used when processing JPA configuration to interpret the user config values. + * + * @param value The encountered config value + * + * @return The matching enum value. An empty value will return {@link #NONE}. + * + * @throws IllegalArgumentException If the incoming value is unrecognized + */ + public static SchemaGenAction interpret(String value) { + if ( StringHelper.isEmpty( value ) ) { + // default is NONE + return NONE; + } + + if ( CREATE.externalName.equals( value ) ) { + return CREATE; + } + else if ( DROP.externalName.equals( value ) ) { + return DROP; + } + else if ( BOTH.externalName.equals( value ) ) { + return BOTH; + } + + throw new IllegalArgumentException( + String.format( + "Unrecognized '%s' or '%s' value : %s", + AvailableSettings.SCHEMA_GEN_DATABASE_ACTION, + AvailableSettings.SCHEMA_GEN_SCRIPTS_ACTION, + value + ) + ); + } + + public boolean includesCreate() { + return this == CREATE || this == BOTH; + } + + public boolean includesDrop() { + return this == DROP || this == BOTH; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + externalName + ")"; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenSource.java new file mode 100644 index 0000000000..9460957c7a --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenSource.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa; + +import org.hibernate.internal.util.StringHelper; + +/** + * Describes the allowable values of the {@value AvailableSettings#SCHEMA_GEN_CREATE_SOURCE} and + * {@value AvailableSettings#SCHEMA_GEN_DROP_SOURCE} settings. + * + * @see AvailableSettings#SCHEMA_GEN_CREATE_SOURCE + * @see AvailableSettings#SCHEMA_GEN_DROP_SOURCE + * + * @author Steve Ebersole + */ +public enum SchemaGenSource { + /** + * "metadata" - The O/RM metadata is used as the exclusive source for generation + */ + METADATA( "metadata" ), + /** + * "scripts" - External DDL script(s) are used as the exclusive source for generation. The scripts for schema + * creation and dropping come from different sources. The creation DDL script is identified by the + * {@value AvailableSettings#SCHEMA_GEN_CREATE_SCRIPT_SOURCE} setting; the drop DDL script is identified by the + * {@value AvailableSettings#SCHEMA_GEN_DROP_SCRIPT_SOURCE} setting. + * + * @see AvailableSettings#SCHEMA_GEN_CREATE_SCRIPT_SOURCE + * @see AvailableSettings#SCHEMA_GEN_DROP_SCRIPT_SOURCE + */ + SCRIPTS( "scripts" ), + /** + * "metadata-then-scripts" - Both the O/RM metadata and external DDL scripts are used as sources for generation, + * with the O/RM metadata being applied first. + * + * @see #METADATA + * @see #SCRIPTS + */ + METADATA_THEN_SCRIPTS( "metadata-then-scripts" ), + /** + * "scripts-then-metadata" - Both the O/RM metadata and external DDL scripts are used as sources for generation, + * with the commands from the external DDL script(s) being applied first + * + * @see #SCRIPTS + * @see #METADATA + */ + SCRIPTS_THEN_METADATA( "scripts-then-metadata" ); + + private final String externalName; + + private SchemaGenSource(String externalName) { + this.externalName = externalName; + } + + /** + * Used when processing JPA configuration to interpret the user config value + * + * @param value The encountered user config value + * + * @return The matching enum value. An empty value will return {@code null}. + * + * @throws IllegalArgumentException If the incoming value is unrecognized + */ + public static SchemaGenSource interpret(String value) { + if ( StringHelper.isEmpty( value ) ) { + // empty is in fact valid as means to interpret default value based on other settings + return null; + } + + if ( METADATA.externalName.equals( value ) ) { + return METADATA; + } + else if ( SCRIPTS.externalName.equals( value ) ) { + return SCRIPTS; + } + else if ( METADATA_THEN_SCRIPTS.externalName.equals( value ) ) { + return METADATA_THEN_SCRIPTS; + } + else if ( SCRIPTS_THEN_METADATA.externalName.equals( value ) ) { + return SCRIPTS_THEN_METADATA; + } + + throw new IllegalArgumentException( "Unrecognized schema generation source value : " + value ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index 8ec697d83d..28e906a8ee 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -75,12 +75,14 @@ import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; import org.hibernate.integrator.spi.Integrator; import org.hibernate.internal.util.StringHelper; import org.hibernate.jaxb.spi.cfg.JaxbHibernateConfiguration.JaxbSessionFactory.JaxbMapping; +import org.hibernate.internal.util.ValueHolder; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.IntegratorProvider; import org.hibernate.jpa.boot.spi.JpaUnifiedSettingsBuilder; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.event.spi.JpaIntegrator; +import org.hibernate.jpa.internal.schemagen.JpaSchemaGenerator; import org.hibernate.jpa.internal.EntityManagerFactoryImpl; import org.hibernate.jpa.internal.EntityManagerMessageLogger; import org.hibernate.jpa.internal.util.LogHelper; @@ -154,6 +156,8 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil private Configuration hibernateConfiguration; private static EntityNotFoundDelegate jpaEntityNotFoundDelegate = new JpaEntityNotFoundDelegate(); + + private ClassLoader providedClassLoader; private static class JpaEntityNotFoundDelegate implements EntityNotFoundDelegate, Serializable { public void handleEntityNotFound(String entityName, Serializable id) { @@ -162,6 +166,14 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } public EntityManagerFactoryBuilderImpl(PersistenceUnitDescriptor persistenceUnit, Map integrationSettings) { + this( persistenceUnit, integrationSettings, null ); + } + + public EntityManagerFactoryBuilderImpl( + PersistenceUnitDescriptor persistenceUnit, + Map integrationSettings, + ClassLoader providedClassLoader ) { + LogHelper.logPersistenceUnitInformation( persistenceUnit ); this.persistenceUnit = persistenceUnit; @@ -169,6 +181,8 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil if ( integrationSettings == null ) { integrationSettings = Collections.emptyMap(); } + + this.providedClassLoader = providedClassLoader; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // First we build the boot-strap service registry, which mainly handles class loader interactions @@ -377,6 +391,185 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } } +<<<<<<< HEAD +======= + /** + * Builds the {@link BootstrapServiceRegistry} used to eventually build the {@link org.hibernate.boot.registry.StandardServiceRegistryBuilder}; mainly + * used here during instantiation to define class-loading behavior. + * + * @param integrationSettings Any integration settings passed by the EE container or SE application + * + * @return The built BootstrapServiceRegistry + */ + private BootstrapServiceRegistry buildBootstrapServiceRegistry(Map integrationSettings) { + final BootstrapServiceRegistryBuilder bootstrapServiceRegistryBuilder = new BootstrapServiceRegistryBuilder(); + bootstrapServiceRegistryBuilder.with( new JpaIntegrator() ); + + final IntegratorProvider integratorProvider = (IntegratorProvider) integrationSettings.get( INTEGRATOR_PROVIDER ); + if ( integratorProvider != null ) { + integrationSettings.remove( INTEGRATOR_PROVIDER ); + for ( Integrator integrator : integratorProvider.getIntegrators() ) { + bootstrapServiceRegistryBuilder.with( integrator ); + } + } + + // TODO: If providedClassLoader is present (OSGi, etc.) *and* + // an APP_CLASSLOADER is provided, should throw an exception or + // warn? + ClassLoader classLoader; + ClassLoader appClassLoader = (ClassLoader) integrationSettings.get( org.hibernate.cfg.AvailableSettings.APP_CLASSLOADER ); + if ( providedClassLoader != null ) { + classLoader = providedClassLoader; + } + else if ( appClassLoader != null ) { + classLoader = appClassLoader; + integrationSettings.remove( org.hibernate.cfg.AvailableSettings.APP_CLASSLOADER ); + } + else { + classLoader = persistenceUnit.getClassLoader(); + } + bootstrapServiceRegistryBuilder.with( classLoader ); + + return bootstrapServiceRegistryBuilder.build(); + } + + @SuppressWarnings("unchecked") + private Map mergePropertySources( + PersistenceUnitDescriptor persistenceUnit, + Map integrationSettings, + final BootstrapServiceRegistry bootstrapServiceRegistry) { + final Map merged = new HashMap(); + // first, apply persistence.xml-defined settings + if ( persistenceUnit.getProperties() != null ) { + merged.putAll( persistenceUnit.getProperties() ); + } + + merged.put( AvailableSettings.PERSISTENCE_UNIT_NAME, persistenceUnit.getName() ); + + // see if the persistence.xml settings named a Hibernate config file.... + final ValueHolder configLoaderHolder = new ValueHolder( + new ValueHolder.DeferredInitializer() { + @Override + public ConfigLoader initialize() { + return new ConfigLoader( bootstrapServiceRegistry ); + } + } + ); + + { + final String cfgXmlResourceName = (String) merged.remove( AvailableSettings.CFG_FILE ); + if ( StringHelper.isNotEmpty( cfgXmlResourceName ) ) { + // it does, so load those properties + JaxbHibernateConfiguration configurationElement = configLoaderHolder.getValue() + .loadConfigXmlResource( cfgXmlResourceName ); + processHibernateConfigurationElement( configurationElement, merged ); + } + } + + // see if integration settings named a Hibernate config file.... + { + final String cfgXmlResourceName = (String) integrationSettings.get( AvailableSettings.CFG_FILE ); + if ( StringHelper.isNotEmpty( cfgXmlResourceName ) ) { + integrationSettings.remove( AvailableSettings.CFG_FILE ); + // it does, so load those properties + JaxbHibernateConfiguration configurationElement = configLoaderHolder.getValue().loadConfigXmlResource( + cfgXmlResourceName + ); + processHibernateConfigurationElement( configurationElement, merged ); + } + } + + // finally, apply integration-supplied settings (per JPA spec, integration settings should override other sources) + merged.putAll( integrationSettings ); + + if ( ! merged.containsKey( AvailableSettings.VALIDATION_MODE ) ) { + if ( persistenceUnit.getValidationMode() != null ) { + merged.put( AvailableSettings.VALIDATION_MODE, persistenceUnit.getValidationMode() ); + } + } + + if ( ! merged.containsKey( AvailableSettings.SHARED_CACHE_MODE ) ) { + if ( persistenceUnit.getSharedCacheMode() != null ) { + merged.put( AvailableSettings.SHARED_CACHE_MODE, persistenceUnit.getSharedCacheMode() ); + } + } + + // was getting NPE exceptions from the underlying map when just using #putAll, so going this safer route... + Iterator itr = merged.entrySet().iterator(); + while ( itr.hasNext() ) { + final Map.Entry entry = (Map.Entry) itr.next(); + if ( entry.getValue() == null ) { + itr.remove(); + } + } + + return merged; + } + + @SuppressWarnings("unchecked") + private void processHibernateConfigurationElement( + JaxbHibernateConfiguration configurationElement, + Map mergeMap) { + if ( ! mergeMap.containsKey( org.hibernate.cfg.AvailableSettings.SESSION_FACTORY_NAME ) ) { + String cfgName = configurationElement.getSessionFactory().getName(); + if ( cfgName != null ) { + mergeMap.put( org.hibernate.cfg.AvailableSettings.SESSION_FACTORY_NAME, cfgName ); + } + } + + for ( JaxbHibernateConfiguration.JaxbSessionFactory.JaxbProperty jaxbProperty : configurationElement.getSessionFactory().getProperty() ) { + mergeMap.put( jaxbProperty.getName(), jaxbProperty.getValue() ); + } + + for ( JaxbHibernateConfiguration.JaxbSessionFactory.JaxbMapping jaxbMapping : configurationElement.getSessionFactory().getMapping() ) { + cfgXmlNamedMappings.add( jaxbMapping ); + } + + for ( Object cacheDeclaration : configurationElement.getSessionFactory().getClassCacheOrCollectionCache() ) { + if ( JaxbHibernateConfiguration.JaxbSessionFactory.JaxbClassCache.class.isInstance( cacheDeclaration ) ) { + final JaxbHibernateConfiguration.JaxbSessionFactory.JaxbClassCache jaxbClassCache + = (JaxbHibernateConfiguration.JaxbSessionFactory.JaxbClassCache) cacheDeclaration; + cacheRegionDefinitions.add( + new CacheRegionDefinition( + CacheRegionDefinition.CacheType.ENTITY, + jaxbClassCache.getClazz(), + jaxbClassCache.getUsage().value(), + jaxbClassCache.getRegion(), + "all".equals( jaxbClassCache.getInclude() ) + ) + ); + } + else { + final JaxbHibernateConfiguration.JaxbSessionFactory.JaxbCollectionCache jaxbCollectionCache + = (JaxbHibernateConfiguration.JaxbSessionFactory.JaxbCollectionCache) cacheDeclaration; + cacheRegionDefinitions.add( + new CacheRegionDefinition( + CacheRegionDefinition.CacheType.COLLECTION, + jaxbCollectionCache.getCollection(), + jaxbCollectionCache.getUsage().value(), + jaxbCollectionCache.getRegion(), + false + ) + ); + } + } + + if ( configurationElement.getSecurity() != null ) { + final String contextId = configurationElement.getSecurity().getContext(); + for ( JaxbHibernateConfiguration.JaxbSecurity.JaxbGrant grant : configurationElement.getSecurity().getGrant() ) { + jaccDefinitions.add( + new JaccDefinition( + contextId, + grant.getRole(), + grant.getEntityName(), + grant.getActions() + ) + ); + } + } + } + +>>>>>>> master private String jaccContextId; private void addJaccDefinition(String key, Object value) { @@ -549,6 +742,12 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil LOG.containerProvidingNullPersistenceUnitRootUrl(); return; } + if ( scanningContext.getUrl().getProtocol().equalsIgnoreCase( "bundle" ) ) { + // TODO: Is there a way to scan the root bundle URL in OSGi containers? + // Although the URL provides a stream handler that works for finding + // resources in a specific Bundle, the root one does not work. + return; + } try { if ( scanningContext.isDetectClasses() ) { @@ -617,6 +816,32 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } + @Override + public void generateSchema() { + processProperties(); + + final ServiceRegistry serviceRegistry = buildServiceRegistry(); + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + + // IMPL NOTE : TCCL handling here is temporary. + // It is needed because this code still uses Hibernate Configuration and Hibernate commons-annotations + // in turn which relies on TCCL being set. + + ( (ClassLoaderServiceImpl) classLoaderService ).withTccl( + new ClassLoaderServiceImpl.Work() { + @Override + public Object perform() { + final Configuration hibernateConfiguration = buildHibernateConfiguration( serviceRegistry ); + JpaSchemaGenerator.performGeneration( hibernateConfiguration, serviceRegistry ); + return null; + } + } + ); + + // release this builder + cancel(); + } + @SuppressWarnings("unchecked") public EntityManagerFactory build() { processProperties(); diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java index bcb70600d7..d593a4f191 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/PersistenceXmlParser.java @@ -108,7 +108,7 @@ public class PersistenceXmlParser { } private List parsePersistenceXml(URL xmlUrl, Map integration) { - // todo : if implementing a "xml binding service" this should be part of it, binding persistence.xml : HHH-6145 + LOG.tracef( "Attempting to parse persistence.xml file : %s" + xmlUrl.toExternalForm() ); final Document doc = loadUrl( xmlUrl ); final Element top = doc.getDocumentElement(); @@ -388,7 +388,7 @@ public class PersistenceXmlParser { private Schema v21Schema() { if ( v21Schema == null ) { - v21Schema = resolveLocalSchema( "org/hibernate/ejb/persistence_2_1.xsd" ); + v21Schema = resolveLocalSchema( "org/hibernate/jpa/persistence_2_1.xsd" ); } return v21Schema; } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/Bootstrap.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/Bootstrap.java index 5790901576..5f22328e11 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/Bootstrap.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/Bootstrap.java @@ -34,6 +34,7 @@ import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; * Entry into the bootstrap process. * * @author Steve Ebersole + * @author Brett Meyer */ public final class Bootstrap { /** @@ -53,6 +54,12 @@ public final class Bootstrap { Map integration) { return new EntityManagerFactoryBuilderImpl( persistenceUnitDescriptor, integration ); } + public static EntityManagerFactoryBuilder getEntityManagerFactoryBuilder( + PersistenceUnitDescriptor persistenceUnitDescriptor, + Map integration, + ClassLoader providedClassLoader) { + return new EntityManagerFactoryBuilderImpl( persistenceUnitDescriptor, integration, providedClassLoader ); + } /** * Builds and returns an EntityManagerFactoryBuilder that can be used to then create an @@ -74,4 +81,11 @@ public final class Bootstrap { Map integration) { return getEntityManagerFactoryBuilder( new PersistenceUnitInfoDescriptor( persistenceUnitInfo ), integration ); } + + public static EntityManagerFactoryBuilder getEntityManagerFactoryBuilder( + PersistenceUnitInfo persistenceUnitInfo, + Map integration, + ClassLoader providedClassLoader) { + return getEntityManagerFactoryBuilder( new PersistenceUnitInfoDescriptor( persistenceUnitInfo ), integration, providedClassLoader ); + } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java index abd50f6a94..dc560c8a82 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java @@ -73,4 +73,9 @@ public interface EntityManagerFactoryBuilder { * something having gone wrong during the bootstrap process */ public void cancel(); + + /** + * Perform an explicit schema generation (rather than an "auto" one) based on the + */ + public void generateSchema(); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java index 67343ff5df..02762f8500 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java @@ -25,8 +25,10 @@ package org.hibernate.jpa.boot.spi; import java.util.Map; -import org.hibernate.ejb.AvailableSettings; +import org.jboss.logging.Logger; + import org.hibernate.ejb.HibernatePersistence; +import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.HibernatePersistenceProvider; /** @@ -36,6 +38,9 @@ import org.hibernate.jpa.HibernatePersistenceProvider; * @author Steve Ebersole */ public class ProviderChecker { + private static final Logger log = Logger.getLogger( ProviderChecker.class ); + + @SuppressWarnings("deprecation") private static String[] HIBERNATE_PROVIDER_NAMES = new String[] { HibernatePersistenceProvider.class.getName(), HibernatePersistence.class.getName() @@ -53,9 +58,7 @@ public class ProviderChecker { */ public static boolean isProvider(PersistenceUnitDescriptor persistenceUnit, Map integration) { // See if we (Hibernate) are the persistence provider - final String requestedProviderName = extractRequestedProviderName( persistenceUnit, integration ); - // NOTE : if no requested name, we assume we are the provider (the calls got to us somehow...) - return requestedProviderName == null || hibernateProviderNamesContain( requestedProviderName ); + return hibernateProviderNamesContain( extractRequestedProviderName( persistenceUnit, integration ) ); } /** @@ -66,11 +69,18 @@ public class ProviderChecker { * @return {@code true} if Hibernate should be the provider; {@code false} otherwise. */ public static boolean hibernateProviderNamesContain(String requestedProviderName) { + log.tracef( + "Checking requested PersistenceProvider name [%s] against Hibernate provider names", + requestedProviderName + ); + for ( String hibernateProviderName : HIBERNATE_PROVIDER_NAMES ) { if ( requestedProviderName.equals( hibernateProviderName ) ) { return true; } } + + log.tracef( "Found no match against Hibernate provider names" ); return false; } @@ -85,15 +95,37 @@ public class ProviderChecker { * @return The extracted provider name, or {@code null} if none found. */ public static String extractRequestedProviderName(PersistenceUnitDescriptor persistenceUnit, Map integration) { - String providerName = integration == null ? null : (String) integration.get( AvailableSettings.PROVIDER ); - if ( providerName == null ) { - providerName = persistenceUnit.getProviderClassName(); + final String integrationProviderName = extractProviderName( integration ); + if ( integrationProviderName != null ) { + log.debugf( "Integration provided explicit PersistenceProvider [%s]", integrationProviderName ); + return integrationProviderName; } - if ( providerName != null ) { - providerName = providerName.trim(); + final String persistenceUnitRequestedProvider = extractProviderName( persistenceUnit ); + if ( persistenceUnitRequestedProvider != null ) { + log.debugf( + "Persistence-unit [%s] requested PersistenceProvider [%s]", + persistenceUnit.getName(), + persistenceUnitRequestedProvider + ); + return persistenceUnitRequestedProvider; } - return providerName; + // NOTE : if no provider requested we assume we are the provider (the calls got to us somehow...) + log.debug( "No PersistenceProvider explicitly requested, assuming Hibernate" ); + return HibernatePersistenceProvider.class.getName(); + } + + private static String extractProviderName(Map integration) { + if ( integration == null ) { + return null; + } + final String setting = (String) integration.get( AvailableSettings.PROVIDER ); + return setting == null ? null : setting.trim(); + } + + private static String extractProviderName(PersistenceUnitDescriptor persistenceUnit) { + final String persistenceUnitRequestedProvider = persistenceUnit.getProviderClassName(); + return persistenceUnitRequestedProvider == null ? null : persistenceUnitRequestedProvider.trim(); } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/PluralAttributePath.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/PluralAttributePath.java index 9c192e0115..ecabcc3056 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/PluralAttributePath.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/PluralAttributePath.java @@ -53,8 +53,7 @@ public class PluralAttributePath extends AbstractPathImpl implements Seria } private static CollectionPersister resolvePersister(CriteriaBuilderImpl criteriaBuilder, PluralAttribute attribute) { - SessionFactoryImplementor sfi = (SessionFactoryImplementor) - criteriaBuilder.getEntityManagerFactory().getSessionFactory(); + SessionFactoryImplementor sfi = criteriaBuilder.getEntityManagerFactory().getSessionFactory(); return sfi.getCollectionPersister( resolveRole( attribute ) ); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostUpdateEventListener.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostUpdateEventListener.java index 42fd9d8249..b25f9795cb 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostUpdateEventListener.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/event/internal/core/JpaPostUpdateEventListener.java @@ -73,8 +73,7 @@ public class JpaPostUpdateEventListener } private void handlePostUpdate(Object entity, EventSource source) { - EntityEntry entry = (EntityEntry) source.getPersistenceContext() - .getEntityEntries().get(entity); + EntityEntry entry = (EntityEntry) source.getPersistenceContext().getEntry( entity ); // mimic the preUpdate filter if ( Status.DELETED != entry.getStatus()) { callbackRegistry.postUpdate(entity); diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java index a3993b9f59..e6ee9231e1 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java @@ -24,6 +24,7 @@ package org.hibernate.jpa.internal; import javax.persistence.Cache; +import javax.persistence.EntityGraph; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceContextType; @@ -32,6 +33,7 @@ import javax.persistence.PersistenceUnitUtil; import javax.persistence.Query; import javax.persistence.SynchronizationType; import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.Metamodel; import javax.persistence.spi.LoadState; import javax.persistence.spi.PersistenceUnitTransactionType; @@ -39,9 +41,12 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.jboss.logging.Logger; @@ -62,6 +67,7 @@ import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.HibernateQuery; import org.hibernate.jpa.boot.internal.SettingsImpl; import org.hibernate.jpa.criteria.CriteriaBuilderImpl; +import org.hibernate.jpa.internal.graph.EntityGraphImpl; import org.hibernate.jpa.internal.util.PersistenceUtilHelper; import org.hibernate.metadata.ClassMetadata; import org.hibernate.service.ServiceRegistry; @@ -90,6 +96,7 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory { private final String entityManagerFactoryName; private final transient PersistenceUtilHelper.MetadataCache cache = new PersistenceUtilHelper.MetadataCache(); + private final transient Map entityGraphs = new ConcurrentHashMap(); @SuppressWarnings( "unchecked" ) public EntityManagerFactoryImpl( @@ -147,7 +154,12 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory { } public EntityManager createEntityManager() { - return createEntityManager( null ); + return createEntityManager( Collections.EMPTY_MAP ); + } + + @Override + public EntityManager createEntityManager(SynchronizationType synchronizationType) { + return createEntityManager( synchronizationType, Collections.EMPTY_MAP ); } public EntityManager createEntityManager(Map map) { @@ -268,6 +280,40 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory { throw new PersistenceException( "Hibernate cannot unwrap EntityManagerFactory as " + cls.getName() ); } + @Override + public void addNamedEntityGraph(String graphName, EntityGraph entityGraph) { + if ( ! EntityGraphImpl.class.isInstance( entityGraph ) ) { + throw new IllegalArgumentException( + "Unknown type of EntityGraph for making named : " + entityGraph.getClass().getName() + ); + } + final EntityGraphImpl copy = ( (EntityGraphImpl) entityGraph ).makeImmutableCopy( graphName ); + final EntityGraphImpl old = entityGraphs.put( graphName, copy ); + if ( old != null ) { + log.debugf( "EntityGraph being replaced on EntityManagerFactory for name %s", graphName ); + } + } + + public EntityGraphImpl findEntityGraphByName(String name) { + return entityGraphs.get( name ); + } + + @SuppressWarnings("unchecked") + public List> findEntityGraphsByType(Class entityClass) { + final EntityType entityType = getMetamodel().entity( entityClass ); + if ( entityType == null ) { + throw new IllegalArgumentException( "Given class is not an entity : " + entityClass.getName() ); + } + + final List> results = new ArrayList>(); + for ( EntityGraphImpl entityGraph : this.entityGraphs.values() ) { + if ( entityGraph.appliesTo( entityType ) ) { + results.add( entityGraph ); + } + } + return results; + } + public boolean isOpen() { return ! sessionFactory.isClosed(); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerImpl.java index 08de2b5a93..e7997c096a 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerImpl.java @@ -23,10 +23,13 @@ */ package org.hibernate.jpa.internal; +import javax.persistence.EntityGraph; import javax.persistence.PersistenceContextType; import javax.persistence.PersistenceException; import javax.persistence.SynchronizationType; +import javax.persistence.metamodel.EntityType; import javax.persistence.spi.PersistenceUnitTransactionType; +import java.util.List; import java.util.Map; import org.jboss.logging.Logger; @@ -40,6 +43,7 @@ import org.hibernate.engine.spi.SessionBuilderImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionOwner; import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.internal.graph.EntityGraphImpl; /** * Hibernate implementation of {@link javax.persistence.EntityManager}. @@ -159,6 +163,35 @@ public class EntityManagerImpl extends AbstractEntityManagerImpl implements Sess } } + @Override + public EntityGraph createEntityGraph(Class rootType) { + return new EntityGraphImpl( null, getMetamodel().entity( rootType ), getEntityManagerFactory() ); + } + + @Override + public EntityGraph createEntityGraph(String graphName) { + final EntityGraphImpl named = getEntityManagerFactory().findEntityGraphByName( graphName ); + if ( named == null ) { + return null; + } + return named.makeMutableCopy(); + } + + @Override + @SuppressWarnings("unchecked") + public EntityGraph getEntityGraph(String graphName) { + final EntityGraphImpl named = getEntityManagerFactory().findEntityGraphByName( graphName ); + if ( named == null ) { + throw new IllegalArgumentException( "Could not locate EntityGraph with given name : " + graphName ); + } + return named; + } + + @Override + public List> getEntityGraphs(Class entityClass) { + return getEntityManagerFactory().findEntityGraphsByType( entityClass ); + } + @Override public boolean shouldAutoCloseSession() { return !isOpen(); diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerMessageLogger.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerMessageLogger.java index 3d3dd9d2a1..09c90d8c84 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerMessageLogger.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerMessageLogger.java @@ -33,6 +33,7 @@ import org.jboss.logging.MessageLogger; import org.hibernate.internal.CoreMessageLogger; +import static org.jboss.logging.Logger.Level.DEBUG; import static org.jboss.logging.Logger.Level.ERROR; import static org.jboss.logging.Logger.Level.INFO; import static org.jboss.logging.Logger.Level.WARN; @@ -114,4 +115,11 @@ public interface EntityManagerMessageLogger extends CoreMessageLogger { @LogMessage( level = INFO ) @Message( value = "Using provided datasource", id = 15012 ) void usingProvidedDataSource(); + + + @LogMessage( level = DEBUG ) + @Message( value = "Returning null (as required by JPA spec) rather than throwing EntityNotFoundException, " + + "as the entity (type=%s, id=%s) does not exist", id = 15013 ) + void ignoringEntityNotFound( String entityName, String identifier); + } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java index 0fc18c8f26..7c4fecffd1 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java @@ -37,11 +37,11 @@ import java.util.List; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockMode; -import org.hibernate.StoredProcedureCall; -import org.hibernate.StoredProcedureOutputs; -import org.hibernate.StoredProcedureResultSetReturn; -import org.hibernate.StoredProcedureReturn; -import org.hibernate.StoredProcedureUpdateCountReturn; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.ProcedureResult; +import org.hibernate.result.ResultSetReturn; +import org.hibernate.result.Return; +import org.hibernate.result.UpdateCountReturn; import org.hibernate.jpa.spi.BaseQueryImpl; import org.hibernate.jpa.spi.HibernateEntityManagerImplementor; @@ -49,60 +49,61 @@ import org.hibernate.jpa.spi.HibernateEntityManagerImplementor; * @author Steve Ebersole */ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredProcedureQuery { - private final StoredProcedureCall storedProcedureCall; - private StoredProcedureOutputs storedProcedureOutputs; + private final ProcedureCall procedureCall; + private ProcedureResult procedureResult; - public StoredProcedureQueryImpl(StoredProcedureCall storedProcedureCall, HibernateEntityManagerImplementor entityManager) { + public StoredProcedureQueryImpl(ProcedureCall procedureCall, HibernateEntityManagerImplementor entityManager) { super( entityManager ); - this.storedProcedureCall = storedProcedureCall; + this.procedureCall = procedureCall; } @Override protected boolean applyTimeoutHint(int timeout) { - storedProcedureCall.setTimeout( timeout ); + procedureCall.setTimeout( timeout ); return true; } @Override protected boolean applyCacheableHint(boolean isCacheable) { - storedProcedureCall.setCacheable( isCacheable ); + procedureCall.setCacheable( isCacheable ); return true; } @Override protected boolean applyCacheRegionHint(String regionName) { - storedProcedureCall.setCacheRegion( regionName ); + procedureCall.setCacheRegion( regionName ); return true; } @Override protected boolean applyReadOnlyHint(boolean isReadOnly) { - storedProcedureCall.setReadOnly( isReadOnly ); + procedureCall.setReadOnly( isReadOnly ); return true; } @Override protected boolean applyCacheModeHint(CacheMode cacheMode) { - storedProcedureCall.setCacheMode( cacheMode ); + procedureCall.setCacheMode( cacheMode ); return true; } @Override protected boolean applyFlushModeHint(FlushMode flushMode) { - storedProcedureCall.setFlushMode( flushMode ); + procedureCall.setFlushMode( flushMode ); return true; } @Override @SuppressWarnings("unchecked") public StoredProcedureQuery registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { - storedProcedureCall.registerStoredProcedureParameter( position, type, mode ); + procedureCall.registerParameter( position, type, mode ); return this; } @Override + @SuppressWarnings("unchecked") public StoredProcedureQuery registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) { - storedProcedureCall.registerStoredProcedureParameter( parameterName, type, mode ); + procedureCall.registerParameter( parameterName, type, mode ); return this; } @@ -171,11 +172,11 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro // outputs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - private StoredProcedureOutputs outputs() { - if ( storedProcedureOutputs == null ) { - storedProcedureOutputs = storedProcedureCall.getOutputs(); + private ProcedureResult outputs() { + if ( procedureResult == null ) { + procedureResult = procedureCall.getResult(); } - return storedProcedureOutputs; + return procedureResult; } @Override @@ -205,29 +206,29 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro @Override public int getUpdateCount() { - final StoredProcedureReturn nextReturn = outputs().getNextReturn(); + final Return nextReturn = outputs().getNextReturn(); if ( nextReturn.isResultSet() ) { return -1; } - return ( (StoredProcedureUpdateCountReturn) nextReturn ).getUpdateCount(); + return ( (UpdateCountReturn) nextReturn ).getUpdateCount(); } @Override public List getResultList() { - final StoredProcedureReturn nextReturn = outputs().getNextReturn(); + final Return nextReturn = outputs().getNextReturn(); if ( ! nextReturn.isResultSet() ) { return null; // todo : what should be thrown/returned here? } - return ( (StoredProcedureResultSetReturn) nextReturn ).getResultList(); + return ( (ResultSetReturn) nextReturn ).getResultList(); } @Override public Object getSingleResult() { - final StoredProcedureReturn nextReturn = outputs().getNextReturn(); + final Return nextReturn = outputs().getNextReturn(); if ( ! nextReturn.isResultSet() ) { return null; // todo : what should be thrown/returned here? } - return ( (StoredProcedureResultSetReturn) nextReturn ).getSingleResult(); + return ( (ResultSetReturn) nextReturn ).getSingleResult(); } @Override diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/AttributeNodeImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/AttributeNodeImpl.java new file mode 100644 index 0000000000..90ec5ea2ff --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/AttributeNodeImpl.java @@ -0,0 +1,267 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.graph; + +import javax.persistence.AttributeNode; +import javax.persistence.Subgraph; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.PluralAttribute; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.jpa.HibernateEntityManagerFactory; +import org.hibernate.jpa.internal.metamodel.Helper; +import org.hibernate.jpa.internal.metamodel.PluralAttributeImpl; +import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Joinable; +import org.hibernate.type.AssociationType; +import org.hibernate.type.Type; + +/** + * Hibernate implementation of the JPA AttributeNode contract + * + * @author Steve Ebersole + */ +public class AttributeNodeImpl implements AttributeNode { + private final HibernateEntityManagerFactory entityManagerFactory; + private final Attribute attribute; + + private Map subgraphMap; + private Map keySubgraphMap; + + public AttributeNodeImpl(HibernateEntityManagerFactory entityManagerFactory, Attribute attribute) { + this.entityManagerFactory = entityManagerFactory; + this.attribute = attribute; + } + + /** + * Intended only for use from {@link #makeImmutableCopy()} + */ + private AttributeNodeImpl( + HibernateEntityManagerFactory entityManagerFactory, + Attribute attribute, + Map subgraphMap, + Map keySubgraphMap) { + this.entityManagerFactory = entityManagerFactory; + this.attribute = attribute; + this.subgraphMap = subgraphMap; + this.keySubgraphMap = keySubgraphMap; + } + + private SessionFactoryImplementor sessionFactory() { + return (SessionFactoryImplementor) entityManagerFactory.getSessionFactory(); + } + + public Attribute getAttribute() { + return attribute; + } + + public String getRegistrationName() { + return getAttributeName(); + } + + @Override + public String getAttributeName() { + return attribute.getName(); + } + + @Override + public Map getSubgraphs() { + return subgraphMap == null ? Collections.emptyMap() : subgraphMap; + } + + @Override + public Map getKeySubgraphs() { + return keySubgraphMap == null ? Collections.emptyMap() : keySubgraphMap; + } + + @SuppressWarnings("unchecked") + public Subgraph makeSubgraph() { + return (Subgraph) makeSubgraph( null ); + } + + @SuppressWarnings("unchecked") + public Subgraph makeSubgraph(Class type) { + if ( attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC + || attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED ) { + throw new IllegalArgumentException( + String.format( "Attribute [%s] is not of managed type", getAttributeName() ) + ); + } + if ( attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION ) { + throw new IllegalArgumentException( + String.format( "Collection elements [%s] is not of managed type", getAttributeName() ) + ); + } + + if ( subgraphMap == null ) { + subgraphMap = new HashMap(); + } + + final AssociationType attributeType = (AssociationType) Helper.resolveType( sessionFactory(), attribute ); + final Joinable joinable = attributeType.getAssociatedJoinable( sessionFactory() ); + + if ( joinable.isCollection() ) { + final EntityPersister elementEntityPersister = ( (QueryableCollection) joinable ).getElementPersister(); + if ( type == null ) { + type = elementEntityPersister.getMappedClass(); + } + else { + if ( !isTreatableAs( elementEntityPersister, type ) ) { + throw new IllegalArgumentException( + String.format( + "Collection elements [%s] cannot be treated as requested type [%s] : %s", + getAttributeName(), + type.getName(), + elementEntityPersister.getMappedClass().getName() + ) + ); + } + } + } + else { + final EntityPersister entityPersister = (EntityPersister) joinable; + if ( type == null ) { + type = entityPersister.getMappedClass(); + } + else { + if ( !isTreatableAs( entityPersister, type ) ) { + throw new IllegalArgumentException( + String.format( + "Attribute [%s] cannot be treated as requested type [%s] : %s", + getAttributeName(), + type.getName(), + entityPersister.getMappedClass().getName() + ) + ); + } + } + } + + final SubgraphImpl subgraph = new SubgraphImpl( this.entityManagerFactory, attribute.getDeclaringType(), type ); + subgraphMap.put( type, subgraph ); + return subgraph; + } + + /** + * Check to make sure that the java type of the given entity persister is treatable as the given type. In other + * words, is the given type a subclass of the class represented by the persister. + * + * @param entityPersister The persister to check + * @param type The type to check it against + * + * @return {@code true} indicates it is treatable as such; {@code false} indicates it is not + */ + @SuppressWarnings("unchecked") + private boolean isTreatableAs(EntityPersister entityPersister, Class type) { + return type.isAssignableFrom( entityPersister.getMappedClass() ); + } + + public Subgraph makeKeySubgraph() { + return (SubgraphImpl) makeKeySubgraph( null ); + } + + @SuppressWarnings("unchecked") + public Subgraph makeKeySubgraph(Class type) { + if ( ! attribute.isCollection() ) { + throw new IllegalArgumentException( + String.format( "Non-collection attribute [%s] cannot be target of key subgraph", getAttributeName() ) + ); + } + + final PluralAttributeImpl pluralAttribute = (PluralAttributeImpl) attribute; + if ( pluralAttribute.getCollectionType() != PluralAttribute.CollectionType.MAP ) { + throw new IllegalArgumentException( + String.format( "Non-Map attribute [%s] cannot be target of key subgraph", getAttributeName() ) + ); + } + + final AssociationType attributeType = (AssociationType) Helper.resolveType( sessionFactory(), attribute ); + final QueryableCollection collectionPersister = (QueryableCollection) attributeType.getAssociatedJoinable( sessionFactory() ); + final Type indexType = collectionPersister.getIndexType(); + + if ( ! indexType.isAssociationType() ) { + throw new IllegalArgumentException( + String.format( "Map index [%s] is not an entity; cannot be target of key subgraph", getAttributeName() ) + ); + } + + if ( keySubgraphMap == null ) { + keySubgraphMap = new HashMap(); + } + + final AssociationType indexAssociationType = (AssociationType) indexType; + final EntityPersister indexEntityPersister = (EntityPersister) indexAssociationType.getAssociatedJoinable( sessionFactory() ); + + if ( type == null ) { + type = indexEntityPersister.getMappedClass(); + } + else { + if ( !isTreatableAs( indexEntityPersister, type ) ) { + throw new IllegalArgumentException( + String.format( + "Map key [%s] cannot be treated as requested type [%s] : %s", + getAttributeName(), + type.getName(), + indexEntityPersister.getMappedClass().getName() + ) + ); + } + } + + final SubgraphImpl subgraph = new SubgraphImpl( this.entityManagerFactory, attribute.getDeclaringType(), type ); + keySubgraphMap.put( type, subgraph ); + return subgraph; + } + + AttributeNodeImpl makeImmutableCopy() { + return new AttributeNodeImpl( + this.entityManagerFactory, + this.attribute, + makeSafeMapCopy( subgraphMap ), + makeSafeMapCopy( keySubgraphMap ) + ); + } + + private static Map makeSafeMapCopy(Map subgraphMap) { + if ( subgraphMap == null ) { + return null; + } + + final int properSize = CollectionHelper.determineProperSizing( subgraphMap ); + final HashMap copy = new HashMap( properSize ); + for ( Map.Entry subgraphEntry : subgraphMap.entrySet() ) { + copy.put( + subgraphEntry.getKey(), + ( ( SubgraphImpl ) subgraphEntry.getValue() ).makeImmutableCopy() + ); + } + return copy; + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/EntityGraphImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/EntityGraphImpl.java new file mode 100644 index 0000000000..afbf92cbe0 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/EntityGraphImpl.java @@ -0,0 +1,162 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.graph; + +import javax.persistence.AttributeNode; +import javax.persistence.EntityGraph; +import javax.persistence.Subgraph; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.IdentifiableType; +import java.util.List; + +import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.jpa.HibernateEntityManagerFactory; + +/** + * The Hibernate implementation of the JPA EntityGraph contract. + * + * @author Steve Ebersole + */ +public class EntityGraphImpl extends GraphNode implements EntityGraph { + private final String name; + private final EntityType entityType; + + public EntityGraphImpl(String name, EntityType entityType, HibernateEntityManagerFactory entityManagerFactory) { + super( entityManagerFactory, true ); + this.name = name; + this.entityType = entityType; + } + + public EntityGraphImpl makeImmutableCopy(String name) { + return new EntityGraphImpl( name, this, false ); + } + + public EntityGraphImpl makeMutableCopy() { + return new EntityGraphImpl( name, this, true ); + } + + private EntityGraphImpl(String name, EntityGraphImpl original, boolean mutable) { + super( original, mutable ); + this.name = name; + this.entityType = original.entityType; + } + + @Override + public String getName() { + return name; + } + + @Override + public void addAttributeNodes(String... attributeNames) { + super.addAttributeNodes( attributeNames ); + } + + @Override + public void addAttributeNodes(Attribute... attributes) { + super.addAttributeNodes( attributes ); + } + + @Override + public Subgraph addSubgraph(Attribute attribute) { + return super.addSubgraph( attribute ); + } + + @Override + public Subgraph addSubgraph(Attribute attribute, Class type) { + return super.addSubgraph( attribute, type ); + } + + @Override + public Subgraph addSubgraph(String attributeName) { + return super.addSubgraph( attributeName ); + } + + @Override + public Subgraph addSubgraph(String attributeName, Class type) { + return super.addSubgraph( attributeName, type ); + } + + @Override + public Subgraph addKeySubgraph(Attribute attribute) { + return super.addKeySubgraph( attribute ); + } + + @Override + public Subgraph addKeySubgraph(Attribute attribute, Class type) { + return super.addKeySubgraph( attribute, type ); + } + + @Override + public Subgraph addKeySubgraph(String attributeName) { + return super.addKeySubgraph( attributeName ); + } + + @Override + public Subgraph addKeySubgraph(String attributeName, Class type) { + return super.addKeySubgraph( attributeName, type ); + } + + @Override + public Subgraph addSubclassSubgraph(Class type) { + // todo : implement + throw new NotYetImplementedException(); + } + + @Override + public List> getAttributeNodes() { + return super.attributeNodes(); + } + + @Override + protected Attribute resolveAttribute(String attributeName) { + final Attribute attribute = entityType.getDeclaredAttribute( attributeName ); + if ( attribute == null ) { + throw new IllegalArgumentException( + String.format( + "Given attribute name [%s] is not an attribute on this entity [%s]", + attributeName, + entityType.getName() + ) + ); + } + return attribute; + } + + public boolean appliesTo(EntityType entityType) { + if ( this.entityType.equals( entityType ) ) { + return true; + } + + IdentifiableType superType = entityType.getSupertype(); + while ( superType != null ) { + if ( superType.equals( entityType ) ) { + return true; + } + superType = superType.getSupertype(); + } + + return false; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/GraphNode.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/GraphNode.java new file mode 100644 index 0000000000..30d4bdfd9b --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/GraphNode.java @@ -0,0 +1,187 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.graph; + +import javax.persistence.AttributeNode; +import javax.persistence.Subgraph; +import javax.persistence.metamodel.Attribute; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jboss.logging.Logger; + +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.jpa.HibernateEntityManagerFactory; + +/** + * @author Steve Ebersole + */ +public abstract class GraphNode { + private static final Logger log = Logger.getLogger( GraphNode.class ); + + private final HibernateEntityManagerFactory entityManagerFactory; + private final boolean mutable; + + private Map> attributeNodeMap; + + protected GraphNode(HibernateEntityManagerFactory entityManagerFactory, boolean mutable) { + this.entityManagerFactory = entityManagerFactory; + this.mutable = mutable; + } + + protected GraphNode(GraphNode original, boolean mutable) { + this.entityManagerFactory = original.entityManagerFactory; + this.mutable = mutable; + this.attributeNodeMap = makeSafeMapCopy( original.attributeNodeMap ); + } + + private static Map> makeSafeMapCopy(Map> attributeNodeMap) { + if ( attributeNodeMap == null ) { + return null; + } + + final int properSize = CollectionHelper.determineProperSizing( attributeNodeMap ); + final HashMap> copy = new HashMap>( properSize ); + for ( Map.Entry> attributeNodeEntry : attributeNodeMap.entrySet() ) { + copy.put( + attributeNodeEntry.getKey(), + ( ( AttributeNodeImpl ) attributeNodeEntry.getValue() ).makeImmutableCopy() + ); + } + return copy; + } + + protected HibernateEntityManagerFactory entityManagerFactory() { + return entityManagerFactory; + } + + protected List> attributeNodes() { + if ( attributeNodeMap == null ) { + return Collections.emptyList(); + } + else { + return new ArrayList>( attributeNodeMap.values() ); + } + } + + protected void addAttributeNodes(String... attributeNames) { + for ( String attributeName : attributeNames ) { + addAttribute( attributeName ); + } + } + + protected AttributeNodeImpl addAttribute(String attributeName) { + return addAttributeNode( buildAttributeNode( attributeName ) ); + } + + @SuppressWarnings("unchecked") + private AttributeNodeImpl buildAttributeNode(String attributeName) { + return buildAttributeNode( resolveAttribute( attributeName ) ); + } + + protected abstract Attribute resolveAttribute(String attributeName); + + protected AttributeNodeImpl buildAttributeNode(Attribute attribute) { + return new AttributeNodeImpl( entityManagerFactory, attribute ); + } + + protected AttributeNodeImpl addAttributeNode(AttributeNodeImpl attributeNode) { + if ( ! mutable ) { + throw new IllegalStateException( "Entity/sub graph is not mutable" ); + } + + if ( attributeNodeMap == null ) { + attributeNodeMap = new HashMap>(); + } + else { + final AttributeNode old = attributeNodeMap.get( attributeNode.getRegistrationName() ); + if ( old != null ) { + log.debugf( + "Encountered request to add entity graph node [%s] using a name [%s] under which another " + + "node is already registered [%s]", + old.getClass().getName(), + attributeNode.getRegistrationName(), + attributeNode.getClass().getName() + ); + } + } + attributeNodeMap.put( attributeNode.getRegistrationName(), attributeNode ); + + return attributeNode; + } + + protected void addAttributeNodes(Attribute... attributes) { + for ( Attribute attribute : attributes ) { + addAttribute( attribute ); + } + } + + @SuppressWarnings("unchecked") + protected AttributeNodeImpl addAttribute(Attribute attribute) { + return addAttributeNode( buildAttributeNode( attribute ) ); + } + + @SuppressWarnings("unchecked") + public Subgraph addSubgraph(Attribute attribute) { + return addAttribute( attribute ).makeSubgraph(); + } + + @SuppressWarnings("unchecked") + public Subgraph addSubgraph(Attribute attribute, Class type) { + return addAttribute( attribute ).makeSubgraph( type ); + } + + @SuppressWarnings("unchecked") + public Subgraph addSubgraph(String attributeName) { + return addAttribute( attributeName ).makeSubgraph(); + } + + @SuppressWarnings("unchecked") + public Subgraph addSubgraph(String attributeName, Class type) { + return addAttribute( attributeName ).makeSubgraph( type ); + } + + @SuppressWarnings("unchecked") + public Subgraph addKeySubgraph(Attribute attribute) { + return addAttribute( attribute ).makeKeySubgraph(); + } + + @SuppressWarnings("unchecked") + public Subgraph addKeySubgraph(Attribute attribute, Class type) { + return addAttribute( attribute ).makeKeySubgraph( type ); + } + + @SuppressWarnings("unchecked") + public Subgraph addKeySubgraph(String attributeName) { + return addAttribute( attributeName ).makeKeySubgraph(); + } + + @SuppressWarnings("unchecked") + public Subgraph addKeySubgraph(String attributeName, Class type) { + return addAttribute( attributeName ).makeKeySubgraph( type ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/SubgraphImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/SubgraphImpl.java new file mode 100644 index 0000000000..0564355cd1 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/graph/SubgraphImpl.java @@ -0,0 +1,136 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.graph; + +import javax.persistence.AttributeNode; +import javax.persistence.Subgraph; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.ManagedType; +import java.util.List; + +import org.hibernate.jpa.HibernateEntityManagerFactory; + +/** + * @author Steve Ebersole + */ +public class SubgraphImpl extends GraphNode implements Subgraph { + private final ManagedType managedType; + private final Class subclass; + + public SubgraphImpl( + HibernateEntityManagerFactory entityManagerFactory, + ManagedType managedType, + Class subclass) { + super( entityManagerFactory, true ); + this.managedType = managedType; + this.subclass = subclass; + } + + private SubgraphImpl(SubgraphImpl original) { + super( original, false ); + this.managedType = original.managedType; + this.subclass = original.subclass; + } + + public SubgraphImpl makeImmutableCopy() { + return new SubgraphImpl( this ); + } + + @Override + public void addAttributeNodes(String... attributeNames) { + super.addAttributeNodes( attributeNames ); + } + + @Override + public void addAttributeNodes(Attribute... attributes) { + super.addAttributeNodes( attributes ); + } + + @Override + public Subgraph addSubgraph(Attribute attribute) { + return super.addSubgraph( attribute ); + } + + @Override + public Subgraph addSubgraph(Attribute attribute, Class type) { + return super.addSubgraph( attribute, type ); + } + + @Override + public Subgraph addSubgraph(String attributeName) { + return super.addSubgraph( attributeName ); + } + + @Override + public Subgraph addSubgraph(String attributeName, Class type) { + return super.addSubgraph( attributeName, type ); + } + + @Override + public Subgraph addKeySubgraph(Attribute attribute) { + return super.addKeySubgraph( attribute ); + } + + @Override + public Subgraph addKeySubgraph(Attribute attribute, Class type) { + return super.addKeySubgraph( attribute, type ); + } + + @Override + public Subgraph addKeySubgraph(String attributeName) { + return super.addKeySubgraph( attributeName ); + } + + @Override + public Subgraph addKeySubgraph(String attributeName, Class type) { + return super.addKeySubgraph( attributeName, type ); + } + + @Override + @SuppressWarnings("unchecked") + public Class getClassType() { + return managedType.getJavaType(); + } + + @Override + public List> getAttributeNodes() { + return super.attributeNodes(); + } + + @Override + @SuppressWarnings("unchecked") + protected Attribute resolveAttribute(String attributeName) { + final Attribute attribute = managedType.getDeclaredAttribute( attributeName ); + if ( attribute == null ) { + throw new IllegalArgumentException( + String.format( + "Given attribute name [%s] is not an attribute on this class [%s]", + attributeName, + managedType.getClass().getName() + ) + ); + } + return attribute; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/instrument/InterceptFieldClassFileTransformer.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/instrument/InterceptFieldClassFileTransformer.java index d1b031747f..51f05e917f 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/instrument/InterceptFieldClassFileTransformer.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/instrument/InterceptFieldClassFileTransformer.java @@ -51,11 +51,11 @@ public class InterceptFieldClassFileTransformer implements javax.persistence.spi }, //TODO change it to a static class to make it faster? new FieldFilter() { - + @Override public boolean shouldInstrumentField(String className, String fieldName) { return true; } - + @Override public boolean shouldTransformFieldAccess( String transformingClassName, String fieldOwnerClassName, String fieldName ) { @@ -64,7 +64,7 @@ public class InterceptFieldClassFileTransformer implements javax.persistence.spi } ); } - + @Override public byte[] transform( ClassLoader loader, String className, diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/metamodel/AbstractManagedType.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/metamodel/AbstractManagedType.java index 21fd30ef3f..b7f846bee7 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/metamodel/AbstractManagedType.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/metamodel/AbstractManagedType.java @@ -138,7 +138,7 @@ public abstract class AbstractManagedType * {@inheritDoc} */ public Attribute getDeclaredAttribute(String name) { - final Attribute attr = declaredSingularAttributes.get( name ); + Attribute attr = declaredAttributes.get( name ); checkNotNull( "Attribute ", attr, name ); return attr; } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/metamodel/Helper.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/metamodel/Helper.java new file mode 100644 index 0000000000..ee73eda378 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/metamodel/Helper.java @@ -0,0 +1,115 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.metamodel; + +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.IdentifiableType; +import javax.persistence.metamodel.ManagedType; + +import org.jboss.logging.Logger; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.CompositeType; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class Helper { + private static final Logger log = Logger.getLogger( Helper.class ); + + private final SessionFactoryImplementor sessionFactory; + + public Helper(SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; + } + + public static interface AttributeSource { + public Type findType(String attributeName); + } + + public AttributeSource resolveAttributeSource(ManagedType managedType) { + return resolveAttributeSource( sessionFactory, managedType ); + } + + public static AttributeSource resolveAttributeSource(SessionFactoryImplementor sessionFactory, ManagedType managedType) { + if ( EmbeddableTypeImpl.class.isInstance( managedType ) ) { + return new ComponentAttributeSource( ( (EmbeddableTypeImpl) managedType ).getHibernateType() ); + } + else if ( IdentifiableType.class.isInstance( managedType ) ) { + final String entityName = managedType.getJavaType().getName(); + log.debugf( "Attempting to resolve managed type as entity using %s", entityName ); + return new EntityPersisterAttributeSource( sessionFactory.getEntityPersister( entityName ) ); + } + else { + throw new IllegalArgumentException( + String.format( "Unknown ManagedType implementation [%s]", managedType.getClass() ) + ); + } + } + + public static class EntityPersisterAttributeSource implements AttributeSource { + private final EntityPersister entityPersister; + + + public EntityPersisterAttributeSource(EntityPersister entityPersister) { + this.entityPersister = entityPersister; + } + + @Override + public Type findType(String attributeName) { + return entityPersister.getPropertyType( attributeName ); + } + } + + public static class ComponentAttributeSource implements AttributeSource { + private final CompositeType compositeType; + + + public ComponentAttributeSource(CompositeType compositeType) { + this.compositeType = compositeType; + } + + @Override + public Type findType(String attributeName) { + int i = 0; + for ( String componentAttributeName : compositeType.getPropertyNames() ) { + if ( attributeName.equals( componentAttributeName ) ) { + return compositeType.getSubtypes()[i]; + } + i++; + } + throw new IllegalArgumentException( "Could not find given attribute name [%s] on composite-type" ); + } + } + + public Type resolveType(Attribute attribute) { + return resolveType( sessionFactory, attribute ); + } + + public static Type resolveType(SessionFactoryImplementor sessionFactory, Attribute attribute) { + return resolveAttributeSource( sessionFactory, attribute.getDeclaringType() ).findType( attribute.getName() ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/DatabaseTarget.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/DatabaseTarget.java new file mode 100644 index 0000000000..c2df6f188c --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/DatabaseTarget.java @@ -0,0 +1,111 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; +import java.sql.Statement; + +import org.jboss.logging.Logger; + +import org.hibernate.jpa.SchemaGenAction; + +/** + * GenerationTarget implementation for handling generation directly to the database + * + * @author Steve Ebersole + */ +class DatabaseTarget implements GenerationTarget { + private static final Logger log = Logger.getLogger( DatabaseTarget.class ); + + private final JdbcConnectionContext jdbcConnectionContext; + private final SchemaGenAction databaseAction; + + private Statement jdbcStatement; + + DatabaseTarget(JdbcConnectionContext jdbcConnectionContext, SchemaGenAction databaseAction) { + this.jdbcConnectionContext = jdbcConnectionContext; + this.databaseAction = databaseAction; + } + + @Override + public void acceptCreateCommands(Iterable commands) { + if ( !databaseAction.includesCreate() ) { + return; + } + + for ( String command : commands ) { + try { + jdbcStatement().execute( command ); + } + catch (SQLException e) { + throw new PersistenceException( + "Unable to execute JPA schema generation create command [" + command + "]" + ); + } + } + } + + private Statement jdbcStatement() { + if ( jdbcStatement == null ) { + try { + jdbcStatement = jdbcConnectionContext.getJdbcConnection().createStatement(); + } + catch (SQLException e) { + throw new PersistenceException( "Unable to generate JDBC Statement object for schema generation" ); + } + } + return jdbcStatement; + } + + @Override + public void acceptDropCommands(Iterable commands) { + if ( !databaseAction.includesDrop() ) { + return; + } + + for ( String command : commands ) { + try { + jdbcStatement().execute( command ); + } + catch (SQLException e) { + throw new PersistenceException( + "Unable to execute JPA schema generation drop command [" + command + "]" + ); + } + } + } + + @Override + public void release() { + if ( jdbcStatement != null ) { + try { + jdbcStatement.close(); + } + catch (SQLException e) { + log.debug( "Unable to close JDBC statement after JPA schema generation : " + e.toString() ); + } + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/FileScriptSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/FileScriptSource.java new file mode 100644 index 0000000000..297c90fb8e --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/FileScriptSource.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; + +import org.jboss.logging.Logger; + +/** + * SqlScriptReader implementation for File references. A reader is opened here and then explicitly closed on + * {@link #reader}. + * + * @author Steve Ebersole + */ +class FileScriptSource extends ReaderScriptSource implements SqlScriptReader { + private static final Logger log = Logger.getLogger( FileScriptSource.class ); + + public FileScriptSource(String fileUrl) { + super( toFileReader( fileUrl ) ); + } + + @Override + public void release() { + try { + reader().close(); + } + catch (IOException e) { + log.warn( "Unable to close file reader for generation script source" ); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static Reader toFileReader(String fileUrl) { + final File file = new File( fileUrl ); + try { + // best effort, since this is very well not allowed in EE environments + file.createNewFile(); + } + catch (Exception e) { + log.debug( "Exception calling File#createNewFile : " + e.toString() ); + } + try { + return new FileReader( file ); + } + catch (IOException e) { + throw new PersistenceException( "Unable to open specified script target file for writing : " + fileUrl ); + } + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationSource.java new file mode 100644 index 0000000000..d7c56ab497 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationSource.java @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +/** + * Contract describing a source for create/drop commands. + * + * @see org.hibernate.jpa.SchemaGenSource + * @see org.hibernate.jpa.AvailableSettings#SCHEMA_GEN_CREATE_SOURCE + * @see org.hibernate.jpa.AvailableSettings#SCHEMA_GEN_DROP_SOURCE + * + * @author Steve Ebersole + */ +interface GenerationSource { + /** + * Retrieve the create generation commands from this source. + * + * @return The generation commands + */ + public Iterable getCommands(); + + /** + * Release this source. Give it a change to release its resources, if any. + */ + public void release(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationTarget.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationTarget.java new file mode 100644 index 0000000000..6e674455fc --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationTarget.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +/** + * Describes a schema generation target + * + * @see org.hibernate.jpa.SchemaGenTarget + * @see org.hibernate.jpa.AvailableSettings#SCHEMA_GEN_TARGET + * + * @author Steve Ebersole + */ +interface GenerationTarget { + /** + * Accept a group of create generation commands + * + * @param commands The commands + */ + public void acceptCreateCommands(Iterable commands); + + /** + * Accept a group of drop generation commands. + * + * @param commands The commands + */ + public void acceptDropCommands(Iterable commands); + + /** + * Release this target, giving it a change to release its resources. + */ + public void release(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JdbcConnectionContext.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JdbcConnectionContext.java new file mode 100644 index 0000000000..cc6d90c534 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JdbcConnectionContext.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.sql.Connection; +import java.sql.SQLException; + +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; + +/** + * Defines access to a JDBC Connection for use in Schema generation + * + * @author Steve Ebersole + */ +class JdbcConnectionContext { + private final JdbcConnectionAccess jdbcConnectionAccess; + private Connection jdbcConnection; + + JdbcConnectionContext(JdbcConnectionAccess jdbcConnectionAccess) { + this.jdbcConnectionAccess = jdbcConnectionAccess; + } + + public Connection getJdbcConnection() { + if ( jdbcConnection == null ) { + try { + this.jdbcConnection = jdbcConnectionAccess.obtainConnection(); + } + catch (SQLException e) { + throw new PersistenceException( "Unable to obtain JDBC Connection", e ); + } + } + return jdbcConnection; + } + + public void release() { + if ( jdbcConnection != null ) { + try { + jdbcConnectionAccess.releaseConnection( jdbcConnection ); + } + catch (SQLException e) { + throw new PersistenceException( "Unable to release JDBC Connection", e ); + } + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JpaSchemaGenerator.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JpaSchemaGenerator.java new file mode 100644 index 0000000000..ca9e06f829 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JpaSchemaGenerator.java @@ -0,0 +1,518 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.io.Reader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.HibernateException; +import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseInfoDialectResolver; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.SchemaGenAction; +import org.hibernate.jpa.SchemaGenSource; +import org.hibernate.mapping.Table; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; + +/** + * Class responsible for the JPA-defined schema generation behavior. + * + * @author Steve Ebersole + */ +public class JpaSchemaGenerator { + private static final Logger log = Logger.getLogger( JpaSchemaGenerator.class ); + + public static void performGeneration(Configuration hibernateConfiguration, ServiceRegistry serviceRegistry) { + + // First, determine the actions (if any) to be performed ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final SchemaGenAction databaseAction = SchemaGenAction.interpret( + hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DATABASE_ACTION ) + ); + final SchemaGenAction scriptsAction = SchemaGenAction.interpret( + hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_SCRIPTS_ACTION ) + ); + + if ( databaseAction == SchemaGenAction.NONE && scriptsAction == SchemaGenAction.NONE ) { + // no actions needed + return; + } + + + // Figure out the JDBC Connection context, if any ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final JdbcConnectionContext jdbcConnectionContext = determineAppropriateJdbcConnectionContext( + hibernateConfiguration, + serviceRegistry + ); + + try { + final Dialect dialect = determineDialect( jdbcConnectionContext, hibernateConfiguration, serviceRegistry ); + + + // determine sources ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List createSourceList = databaseAction.includesCreate() || scriptsAction.includesCreate() + ? buildCreateSourceList( hibernateConfiguration, serviceRegistry, dialect ) + : Collections.emptyList(); + + final List dropSourceList = databaseAction.includesDrop() || scriptsAction.includesDrop() + ? buildDropSourceList( hibernateConfiguration, serviceRegistry, dialect ) + : Collections.emptyList(); + + + // determine targets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final GenerationTarget databaseTarget = new DatabaseTarget( jdbcConnectionContext, databaseAction ); + + final Object createScriptTargetSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_SCRIPTS_CREATE_TARGET + ); + final Object dropScriptTargetSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_SCRIPTS_DROP_TARGET + ); + final GenerationTarget scriptsTarget = new ScriptsTarget( createScriptTargetSetting, dropScriptTargetSetting, scriptsAction ); + + final List targets = Arrays.asList( databaseTarget, scriptsTarget ); + + + // finally, do the generation + + try { + doGeneration( createSourceList, dropSourceList, targets ); + } + finally { + releaseTargets( targets ); + releaseSources( createSourceList ); + releaseSources( dropSourceList ); + } + } + finally { + releaseJdbcConnectionContext( jdbcConnectionContext ); + } + } + + private static List buildCreateSourceList( + Configuration hibernateConfiguration, + ServiceRegistry serviceRegistry, + Dialect dialect) { + final List generationSourceList = new ArrayList(); + + final boolean createSchemas = ConfigurationHelper.getBoolean( + AvailableSettings.SCHEMA_GEN_CREATE_SCHEMAS, + hibernateConfiguration.getProperties(), + false + ); + if ( createSchemas ) { + generationSourceList.add( new CreateSchemaCommandSource( hibernateConfiguration, dialect ) ); + } + + SchemaGenSource sourceType = SchemaGenSource.interpret( + hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_CREATE_SOURCE ) + ); + + final Object createScriptSourceSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_CREATE_SCRIPT_SOURCE + ); + + if ( sourceType == null ) { + if ( createScriptSourceSetting != null ) { + sourceType = SchemaGenSource.SCRIPTS; + } + else { + sourceType = SchemaGenSource.METADATA; + } + } + + final ImportSqlCommandExtractor scriptCommandExtractor = serviceRegistry.getService( ImportSqlCommandExtractor.class ); + + if ( sourceType == SchemaGenSource.METADATA ) { + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect, true ) ); + } + else if ( sourceType == SchemaGenSource.SCRIPTS ) { + generationSourceList.add( new ScriptSource( createScriptSourceSetting, scriptCommandExtractor ) ); + } + else if ( sourceType == SchemaGenSource.METADATA_THEN_SCRIPTS ) { + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect, true ) ); + generationSourceList.add( new ScriptSource( createScriptSourceSetting, scriptCommandExtractor ) ); + } + else if ( sourceType == SchemaGenSource.SCRIPTS_THEN_METADATA ) { + generationSourceList.add( new ScriptSource( createScriptSourceSetting, scriptCommandExtractor ) ); + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect, true ) ); + } + + final Object importScriptSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_LOAD_SCRIPT_SOURCE + ); + if ( importScriptSetting != null ) { + generationSourceList.add( new ImportScriptSource( importScriptSetting, scriptCommandExtractor ) ); + } + + return generationSourceList; + } + + private static List buildDropSourceList( + Configuration hibernateConfiguration, + ServiceRegistry serviceRegistry, + Dialect dialect) { + final List generationSourceList = new ArrayList(); + + SchemaGenSource sourceType = SchemaGenSource.interpret( + hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DROP_SOURCE ) + ); + + final Object dropScriptSourceSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_DROP_SCRIPT_SOURCE + ); + + if ( sourceType == null ) { + if ( dropScriptSourceSetting != null ) { + sourceType = SchemaGenSource.SCRIPTS; + } + else { + sourceType = SchemaGenSource.METADATA; + } + } + + final ImportSqlCommandExtractor scriptCommandExtractor = serviceRegistry.getService( ImportSqlCommandExtractor.class ); + + if ( sourceType == SchemaGenSource.METADATA ) { + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect, false ) ); + } + else if ( sourceType == SchemaGenSource.SCRIPTS ) { + generationSourceList.add( new ScriptSource( dropScriptSourceSetting, scriptCommandExtractor ) ); + } + else if ( sourceType == SchemaGenSource.METADATA_THEN_SCRIPTS ) { + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect, false ) ); + generationSourceList.add( new ScriptSource( dropScriptSourceSetting, scriptCommandExtractor ) ); + } + else if ( sourceType == SchemaGenSource.SCRIPTS_THEN_METADATA ) { + generationSourceList.add( new ScriptSource( dropScriptSourceSetting, scriptCommandExtractor ) ); + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect, false ) ); + } + + return generationSourceList; + } + + private static JdbcConnectionContext determineAppropriateJdbcConnectionContext( + Configuration hibernateConfiguration, + ServiceRegistry serviceRegistry) { + // see if a specific connection has been provided: + final Connection providedConnection = (Connection) hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_CONNECTION + ); + + if ( providedConnection != null ) { + return new JdbcConnectionContext( + new JdbcConnectionAccess() { + @Override + public Connection obtainConnection() throws SQLException { + return providedConnection; + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + // do nothing + } + + @Override + public boolean supportsAggressiveRelease() { + return false; + } + } + ); + } + + final ConnectionProvider connectionProvider = serviceRegistry.getService( ConnectionProvider.class ); + if ( connectionProvider != null ) { + return new JdbcConnectionContext( + new JdbcConnectionAccess() { + @Override + public Connection obtainConnection() throws SQLException { + return connectionProvider.getConnection(); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + connectionProvider.closeConnection( connection ); + } + + @Override + public boolean supportsAggressiveRelease() { + return connectionProvider.supportsAggressiveRelease(); + } + } + ); + } + + // otherwise, return a no-op impl + return new JdbcConnectionContext( null ) { + @Override + public Connection getJdbcConnection() { + throw new PersistenceException( "No connection information supplied" ); + } + }; + } + + private static Dialect determineDialect( + JdbcConnectionContext jdbcConnectionContext, + Configuration hibernateConfiguration, + ServiceRegistry serviceRegistry) { + final String explicitDbName = hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DB_NAME ); + final String explicitDbMajor = hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DB_MAJOR_VERSION ); + final String explicitDbMinor = hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DB_MINOR_VERSION ); + + if ( StringHelper.isNotEmpty( explicitDbName ) ) { + serviceRegistry.getService( DatabaseInfoDialectResolver.class ).resolve( + new DatabaseInfoDialectResolver.DatabaseInfo() { + @Override + public String getDatabaseName() { + return explicitDbName; + } + + @Override + public int getDatabaseMajorVersion() { + return StringHelper.isEmpty( explicitDbMajor ) + ? NO_VERSION + : Integer.parseInt( explicitDbMajor ); + } + + @Override + public int getDatabaseMinorVersion() { + return StringHelper.isEmpty( explicitDbMinor ) + ? NO_VERSION + : Integer.parseInt( explicitDbMinor ); + } + } + ); + } + + return buildDialect( hibernateConfiguration, serviceRegistry, jdbcConnectionContext ); + } + + private static Dialect buildDialect( + Configuration hibernateConfiguration, + ServiceRegistry serviceRegistry, + JdbcConnectionContext jdbcConnectionContext) { + // todo : a lot of copy/paste from the DialectFactory impl... + final String dialectName = hibernateConfiguration.getProperty( org.hibernate.cfg.AvailableSettings.DIALECT ); + if ( dialectName != null ) { + return constructDialect( dialectName, serviceRegistry ); + } + else { + return determineDialectBasedOnJdbcMetadata( jdbcConnectionContext, serviceRegistry ); + } + } + + private static Dialect constructDialect(String dialectName, ServiceRegistry serviceRegistry) { + final Dialect dialect; + final StrategySelector strategySelector = serviceRegistry.getService( StrategySelector.class ); + try { + dialect = strategySelector.resolveStrategy( Dialect.class, dialectName ); + if ( dialect == null ) { + throw new HibernateException( "Unable to construct requested dialect [" + dialectName + "]" ); + } + return dialect; + } + catch (HibernateException e) { + throw e; + } + catch (Exception e) { + throw new HibernateException( "Unable to construct requested dialect [" + dialectName+ "]", e ); + } + } + + private static Dialect determineDialectBasedOnJdbcMetadata( + JdbcConnectionContext jdbcConnectionContext, + ServiceRegistry serviceRegistry) { + DialectResolver dialectResolver = serviceRegistry.getService( DialectResolver.class ); + try { + final DatabaseMetaData databaseMetaData = jdbcConnectionContext.getJdbcConnection().getMetaData(); + final Dialect dialect = dialectResolver.resolveDialect( databaseMetaData ); + + if ( dialect == null ) { + throw new HibernateException( + "Unable to determine Dialect to use [name=" + databaseMetaData.getDatabaseProductName() + + ", majorVersion=" + databaseMetaData.getDatabaseMajorVersion() + + "]; user must register resolver or explicitly set 'hibernate.dialect'" + ); + } + + return dialect; + } + catch ( SQLException sqlException ) { + throw new HibernateException( + "Unable to access java.sql.DatabaseMetaData to determine appropriate Dialect to use", + sqlException + ); + } + } + + private static void doGeneration( + List createSourceList, + List dropSourceList, + List targets) { + for ( GenerationTarget target : targets ) { + for ( GenerationSource source : createSourceList ) { + target.acceptCreateCommands( source.getCommands() ); + } + + for ( GenerationSource source : dropSourceList ) { + target.acceptDropCommands( source.getCommands() ); + } + } + } + + private static void releaseSources(List generationSourceList ) { + for ( GenerationSource source : generationSourceList ) { + try { + source.release(); + } + catch (Exception e) { + log.debug( "Problem releasing generation source : " + e.toString() ); + } + } + } + + private static void releaseTargets(List generationTargetList) { + for ( GenerationTarget target : generationTargetList ) { + try { + target.release(); + } + catch (Exception e) { + log.debug( "Problem releasing generation target : " + e.toString() ); + } + } + } + + private static void releaseJdbcConnectionContext(JdbcConnectionContext jdbcConnectionContext) { + try { + jdbcConnectionContext.release(); + } + catch (Exception e) { + log.debug( "Unable to release JDBC connection after generation" ); + } + } + + + private static class CreateSchemaCommandSource implements GenerationSource { + private final List commands; + + private CreateSchemaCommandSource(Configuration hibernateConfiguration, Dialect dialect) { + // NOTES: + // 1) catalogs are currently not handled here at all + // 2) schemas for sequences are not handled here at all + // Both of these are handle-able on the metamodel codebase + + final HashSet schemas = new HashSet(); +// final HashSet catalogs = new HashSet(); + + final Iterator tables = hibernateConfiguration.getTableMappings(); + while ( tables.hasNext() ) { + final Table table = tables.next(); +// catalogs.add( table.getCatalog() ); + schemas.add( table.getSchema() ); + } + +// final Iterator generators = hibernateConfiguration.iterateGenerators( dialect ); +// while ( generators.hasNext() ) { +// final IdentifierGenerator generator = generators.next(); +// if ( PersistentIdentifierGenerator.class.isInstance( generator ) ) { +//// catalogs.add( ( (PersistentIdentifierGenerator) generator ).getCatalog() ); +// schemas.add( ( (PersistentIdentifierGenerator) generator ).getSchema() ); +// } +// } + +// if ( schemas.isEmpty() && catalogs.isEmpty() ) { + if ( schemas.isEmpty() ) { + commands = Collections.emptyList(); + return; + } + + commands = new ArrayList(); + + for ( String schema : schemas ) { + commands.add( dialect.getCreateSchemaCommand( schema ) ); + } + + // generate "create catalog" commands + } + + @Override + public Iterable getCommands() { + return commands; + } + + @Override + public void release() { + // nothing to do + } + } + + private static class ImportScriptSource implements GenerationSource { + private final SqlScriptReader sourceReader; + private final ImportSqlCommandExtractor scriptCommandExtractor; + + public ImportScriptSource(Object scriptSourceSetting, ImportSqlCommandExtractor scriptCommandExtractor) { + this.scriptCommandExtractor = scriptCommandExtractor; + + if ( Reader.class.isInstance( scriptSourceSetting ) ) { + sourceReader = new ReaderScriptSource( (Reader) scriptSourceSetting ); + } + else { + sourceReader = new FileScriptSource( scriptSourceSetting.toString() ); + } + } + + @Override + public Iterable getCommands() { + return sourceReader.read( scriptCommandExtractor ); + } + + @Override + public void release() { + sourceReader.release(); + } + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/MetadataSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/MetadataSource.java new file mode 100644 index 0000000000..677985b324 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/MetadataSource.java @@ -0,0 +1,56 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import java.util.Arrays; + +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; + +/** + * @author Steve Ebersole + */ +public class MetadataSource implements GenerationSource { + private final Configuration hibernateConfiguration; + private final Dialect dialect; + private final boolean creation; + + public MetadataSource(Configuration hibernateConfiguration, Dialect dialect, boolean creation) { + this.hibernateConfiguration = hibernateConfiguration; + this.dialect = dialect; + this.creation = creation; + } + + @Override + public Iterable getCommands() { + return creation + ? Arrays.asList( hibernateConfiguration.generateSchemaCreationScript( dialect ) ) + : Arrays.asList( hibernateConfiguration.generateDropSchemaScript( dialect ) ); + } + + @Override + public void release() { + // nothing to do + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ReaderScriptSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ReaderScriptSource.java new file mode 100644 index 0000000000..8127a7abe0 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ReaderScriptSource.java @@ -0,0 +1,63 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import java.io.Reader; +import java.util.Arrays; +import java.util.Collections; + +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; + +/** + * SqlScriptReader implementation for explicitly given Readers. The readers are not released by this class. + * + * @author Steve Ebersole + */ +class ReaderScriptSource implements SqlScriptReader { + private final Reader reader; + + public ReaderScriptSource(Reader reader) { + this.reader = reader; + } + + @Override + public Iterable read(ImportSqlCommandExtractor commandExtractor) { + final String[] commands = commandExtractor.extractCommands( reader ); + if ( commands == null ) { + return Collections.emptyList(); + } + else { + return Arrays.asList( commands ); + } + } + + @Override + public void release() { + // nothing to do here + } + + protected Reader reader() { + return reader; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ImplicitStatementProxyHandler.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptSource.java similarity index 53% rename from hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ImplicitStatementProxyHandler.java rename to hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptSource.java index 29a3eedc35..d00e4ad838 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/proxy/ImplicitStatementProxyHandler.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptSource.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. @@ -21,27 +21,38 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.engine.jdbc.internal.proxy; -import java.lang.reflect.Method; -import java.sql.Connection; -import java.sql.Statement; +package org.hibernate.jpa.internal.schemagen; -import org.hibernate.HibernateException; +import java.io.Reader; + +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; /** - * Invocation handler for {@link java.sql.Statement} proxies obtained from other JDBC object proxies - * * @author Steve Ebersole */ -public class ImplicitStatementProxyHandler extends AbstractStatementProxyHandler { - protected ImplicitStatementProxyHandler(Statement statement, ConnectionProxyHandler connectionProxyHandler, Connection connectionProxy) { - super( statement, connectionProxyHandler, connectionProxy ); - } +public class ScriptSource implements GenerationSource { + private final SqlScriptReader reader; + private final ImportSqlCommandExtractor scriptCommandExtractor; - protected void beginningInvocationHandling(Method method, Object[] args) { - // disallow executions... - if ( method.getName().startsWith( "execute" ) ) { - throw new HibernateException( "execution not allowed on implicit statement object" ); + public ScriptSource(Object scriptSourceSetting, ImportSqlCommandExtractor scriptCommandExtractor) { + this.scriptCommandExtractor = scriptCommandExtractor; + + if ( Reader.class.isInstance( scriptSourceSetting ) ) { + reader = new ReaderScriptSource( (Reader) scriptSourceSetting ); + } + else { + reader = new FileScriptSource( scriptSourceSetting.toString() ); } } + + @Override + public Iterable getCommands() { + return reader.read( scriptCommandExtractor ); + } + + @Override + public void release() { + reader.release(); + } + } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptsTarget.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptsTarget.java new file mode 100644 index 0000000000..b696f2572f --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptsTarget.java @@ -0,0 +1,195 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; + +import org.jboss.logging.Logger; + +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.SchemaGenAction; + +/** + * GenerationTarget implementation for handling generation to scripts + * + * @author Steve Ebersole + */ +class ScriptsTarget implements GenerationTarget { + private static final Logger log = Logger.getLogger( ScriptsTarget.class ); + + private final ScriptTargetTarget createScriptTarget; + private final ScriptTargetTarget dropScriptTarget; + private final SchemaGenAction scriptsAction; + + public ScriptsTarget( + Object createScriptTargetSetting, + Object dropScriptTargetSetting, + SchemaGenAction scriptsAction) { + this.scriptsAction = scriptsAction; + + if ( scriptsAction.includesCreate() ) { + if ( Writer.class.isInstance( createScriptTargetSetting ) ) { + createScriptTarget = new WriterScriptTarget( (Writer) createScriptTargetSetting ); + } + else { + createScriptTarget = new FileScriptTarget( createScriptTargetSetting.toString() ); + } + } + else { + if ( createScriptTargetSetting != null ) { + // the wording in the spec hints that this maybe should be an error, but does not explicitly + // call out an exception to use. + log.debugf( + "Value was specified for '%s' [%s], but create scripting was not requested", + AvailableSettings.SCHEMA_GEN_SCRIPTS_CREATE_TARGET, + createScriptTargetSetting + ); + } + createScriptTarget = null; + } + + if ( scriptsAction.includesDrop() ) { + if ( Writer.class.isInstance( dropScriptTargetSetting ) ) { + dropScriptTarget = new WriterScriptTarget( (Writer) dropScriptTargetSetting ); + } + else { + dropScriptTarget = new FileScriptTarget( dropScriptTargetSetting.toString() ); + } + } + else { + if ( dropScriptTargetSetting != null ) { + // the wording in the spec hints that this maybe should be an error, but does not explicitly + // call out an exception to use. + log.debugf( + "Value was specified for '%s' [%s], but drop scripting was not requested", + AvailableSettings.SCHEMA_GEN_SCRIPTS_DROP_TARGET, + dropScriptTargetSetting + ); + } + dropScriptTarget = null; + } + } + + @Override + public void acceptCreateCommands(Iterable commands) { + if ( ! scriptsAction.includesCreate() ) { + return; + } + + for ( String command : commands ) { + createScriptTarget.accept( command ); + } + } + + @Override + public void acceptDropCommands(Iterable commands) { + if ( ! scriptsAction.includesDrop() ) { + return; + } + + for ( String command : commands ) { + dropScriptTarget.accept( command ); + } + } + + @Override + public void release() { + createScriptTarget.release(); + dropScriptTarget.release(); + } + + /** + * Internal contract for handling Writer/File differences + */ + private static interface ScriptTargetTarget { + public void accept(String command); + public void release(); + } + + private static class WriterScriptTarget implements ScriptTargetTarget { + private final Writer writer; + + public WriterScriptTarget(Writer writer) { + this.writer = writer; + } + + @Override + public void accept(String command) { + try { + writer.write( command ); + writer.flush(); + } + catch (IOException e) { + throw new PersistenceException( "Could not write to target script file", e ); + } + } + + @Override + public void release() { + // nothing to do for a supplied writer + } + + protected Writer writer() { + return writer; + } + } + + private static class FileScriptTarget extends WriterScriptTarget implements ScriptTargetTarget { + public FileScriptTarget(String fileUrl) { + super( toFileWriter( fileUrl ) ); + } + + @Override + public void release() { + try { + writer().close(); + } + catch (IOException e) { + throw new PersistenceException( "Unable to close file writer : " + e.toString() ); + } + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static Writer toFileWriter(String fileUrl) { + final File file = new File( fileUrl ); + try { + // best effort, since this is very well not allowed in EE environments + file.createNewFile(); + } + catch (Exception e) { + log.debug( "Exception calling File#createNewFile : " + e.toString() ); + } + try { + return new FileWriter( file ); + } + catch (IOException e) { + throw new PersistenceException( "Unable to open specified script target file for writing : " + fileUrl ); + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/SqlScriptReader.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/SqlScriptReader.java new file mode 100644 index 0000000000..d36cb2d93b --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/SqlScriptReader.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; + +/** + * Contract for handling Reader/File differences + * + * @author Steve Ebersole + */ +public interface SqlScriptReader { + public Iterable read(ImportSqlCommandExtractor commandExtractor); + public void release(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java index 2f408bc694..f7dbe26361 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/PersistenceUtilHelper.java @@ -55,7 +55,7 @@ public class PersistenceUtilHelper { //it's ours so we know it's loaded if (state == LoadState.UNKNOWN) state = LoadState.LOADED; } - else if ( interceptor != null && (! isInitialized)) { + else if ( interceptor != null ) { state = LoadState.NOT_LOADED; } else if ( sureFromUs ) { //interceptor == null diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/XmlHelper.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/XmlHelper.java index 01c70ea5b6..3f1f01fabe 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/XmlHelper.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/util/XmlHelper.java @@ -58,7 +58,7 @@ public final class XmlHelper { Node currentChild = children.item( i ); if ( currentChild.getNodeType() == Node.ELEMENT_NODE && ( (Element) currentChild ).getTagName().equals( tagName ) ) { - goodChildren.add( (Element) currentChild ); + goodChildren.add( currentChild ); } } return goodChildren.iterator(); diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java index 74d2444f5e..229651d1f5 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java @@ -83,7 +83,7 @@ import org.hibernate.SQLQuery; import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; -import org.hibernate.StoredProcedureCall; +import org.hibernate.procedure.ProcedureCall; import org.hibernate.TransientObjectException; import org.hibernate.TypeMismatchException; import org.hibernate.UnresolvableObjectException; @@ -285,7 +285,10 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage throw new PersistenceException( "Unable to parse " + AvailableSettings.LOCK_TIMEOUT + ": " + lockTimeout ); } if ( timeoutSet ) { - if ( timeout < 0 ) { + if ( timeout == LockOptions.SKIP_LOCKED ) { + options.setTimeOut( LockOptions.SKIP_LOCKED ); + } + else if ( timeout < 0 ) { options.setTimeOut( LockOptions.WAIT_FOREVER ); } else if ( timeout == 0 ) { @@ -813,8 +816,8 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage @Override public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { try { - StoredProcedureCall call = getSession().createStoredProcedureCall( procedureName ); - return new StoredProcedureQueryImpl( call, this ); + ProcedureCall procedureCall = getSession().createStoredProcedureCall( procedureName ); + return new StoredProcedureQueryImpl( procedureCall, this ); } catch ( HibernateException he ) { throw convert( he ); @@ -824,8 +827,8 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage @Override public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { try { - StoredProcedureCall call = getSession().createStoredProcedureCall( procedureName, resultClasses ); - return new StoredProcedureQueryImpl( call, this ); + ProcedureCall procedureCall = getSession().createStoredProcedureCall( procedureName, resultClasses ); + return new StoredProcedureQueryImpl( procedureCall, this ); } catch ( HibernateException he ) { throw convert( he ); @@ -887,6 +890,16 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage return ( A ) getSession().get( entityClass, ( Serializable ) primaryKey ); } } + catch ( EntityNotFoundException ignored ) { + // DefaultLoadEventListener.returnNarrowedProxy may throw ENFE (see HHH-7861 for details), + // which find() should not throw. Find() should return null if the entity was not found. + if ( LOG.isDebugEnabled() ) { + String entityName = entityClass != null ? entityClass.getName(): null; + String identifierValue = primaryKey != null ? primaryKey.toString() : null ; + LOG.ignoringEntityNotFound( entityName, identifierValue ); + } + return null; + } catch ( ObjectDeletedException e ) { //the spec is silent about people doing remove() find() on the same PC return null; diff --git a/hibernate-entitymanager/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider b/hibernate-entitymanager/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider index fdd5294c3b..f460306835 100644 --- a/hibernate-entitymanager/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider +++ b/hibernate-entitymanager/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider @@ -1 +1 @@ -org.hibernate.ejb.HibernatePersistence +org.hibernate.jpa.HibernatePersistenceProvider \ No newline at end of file diff --git a/hibernate-entitymanager/src/main/resources/org/hibernate/jpa/orm_2_1.xsd b/hibernate-entitymanager/src/main/resources/org/hibernate/jpa/orm_2_1.xsd new file mode 100644 index 0000000000..9737596c1a --- /dev/null +++ b/hibernate-entitymanager/src/main/resources/org/hibernate/jpa/orm_2_1.xsd @@ -0,0 +1,2295 @@ + + + + + + + @(#)orm_2_1.xsd 2.1 February 4 2013 + + + + + + ... + + + + ]]> + + + + + + + + + + + + + + + + + + The entity-mappings element is the root element of a mapping + file. It contains the following four types of elements: + + 1. The persistence-unit-metadata element contains metadata + for the entire persistence unit. It is undefined if this element + occurs in multiple mapping files within the same persistence unit. + + 2. The package, schema, catalog and access elements apply to all of + the entity, mapped-superclass and embeddable elements defined in + the same file in which they occur. + + 3. The sequence-generator, table-generator, converter, named-query, + named-native-query, named-stored-procedure-query, and + sql-result-set-mapping elements are global to the persistence + unit. It is undefined to have more than one sequence-generator + or table-generator of the same name in the same or different + mapping files in a persistence unit. It is undefined to have + more than one named-query, named-native-query, sql-result-set-mapping, + or named-stored-procedure-query of the same name in the same + or different mapping files in a persistence unit. It is also + undefined to have more than one converter for the same target + type in the same or different mapping files in a persistence unit. + + 4. The entity, mapped-superclass and embeddable elements each define + the mapping information for a managed persistent class. The mapping + information contained in these elements may be complete or it may + be partial. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Metadata that applies to the persistence unit and not just to + the mapping file in which it is contained. + + If the xml-mapping-metadata-complete element is specified, + the complete set of mapping metadata for the persistence unit + is contained in the XML mapping files for the persistence unit. + + + + + + + + + + + + + + + + + These defaults are applied to the persistence unit as a whole + unless they are overridden by local annotation or XML + element settings. + + schema - Used as the schema for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + catalog - Used as the catalog for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + delimited-identifiers - Used to treat database identifiers as + delimited identifiers. + access - Used as the access type for all managed classes in + the persistence unit + cascade-persist - Adds cascade-persist to the set of cascade options + in all entity relationships of the persistence unit + entity-listeners - List of default entity listeners to be invoked + on each entity in the persistence unit. + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for an entity. Is allowed to be + sparsely populated and used in conjunction with the annotations. + Alternatively, the metadata-complete attribute can be used to + indicate that no annotations on the entity class (and its fields + or properties) are to be processed. If this is the case then + the defaulting rules for the entity and its subelements will + be recursively applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface Entity { + String name() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This element determines how the persistence provider accesses the + state of an entity or embedded object. + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AssociationOverride { + String name(); + JoinColumn[] joinColumns() default{}; + JoinTable joinTable() default @JoinTable; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AttributeOverride { + String name(); + Column column(); + } + + + + + + + + + + + + + + + + + This element contains the entity field or property mappings. + It may be sparsely populated to include only a subset of the + fields or properties. If metadata-complete for the entity is true + then the remainder of the attributes will be defaulted according + to the default rules. + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Basic { + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH}; + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface CollectionTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Column { + String name() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ColumnResult { + String name(); + Class type() default void.class; + } + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ConstructorResult { + Class targetClass(); + ColumnResult[] columns(); + } + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Convert { + Class converter() default void.class; + String attributeName() default ""; + boolean disableConversion() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Converter { + boolean autoApply() default false; + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorColumn { + String name() default "DTYPE"; + DiscriminatorType discriminatorType() default STRING; + String columnDefinition() default ""; + int length() default 31; + } + + + + + + + + + + + + + + + + public enum DiscriminatorType { STRING, CHAR, INTEGER }; + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorValue { + String value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ElementCollection { + Class targetClass() default void.class; + FetchType fetch() default LAZY; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for embeddable objects. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + in the class. If this is the case then the defaulting rules will + be recursively applied. + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Embeddable {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Embedded {} + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface EmbeddedId {} + + + + + + + + + + + + + + + + + Defines an entity listener to be invoked at lifecycle events + for the entities that list this listener. + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface EntityListeners { + Class[] value(); + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface EntityResult { + Class entityClass(); + FieldResult[] fields() default {}; + String discriminatorColumn() default ""; + } + + + + + + + + + + + + + + + + + public enum EnumType { + ORDINAL, + STRING + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Enumerated { + EnumType value() default ORDINAL; + } + + + + + + + + + + + + + public enum FetchType { LAZY, EAGER }; + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface FieldResult { + String name(); + String column(); + } + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ForeignKey { + String name() default ""; + String foreign-key-definition() default ""; + boolean disable-foreign-key() default false; + } + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface GeneratedValue { + GenerationType strategy() default AUTO; + String generator() default ""; + } + + + + + + + + + + + + + + public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO }; + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Id {} + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface IdClass { + Class value(); + } + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface Index { + String name() default ""; + String columnList(); + boolean unique() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Inheritance { + InheritanceType strategy() default SINGLE_TABLE; + } + + + + + + + + + + + + + public enum InheritanceType + { SINGLE_TABLE, JOINED, TABLE_PER_CLASS}; + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + ForeignKey foreignKey() default @ForeignKey(); + } + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + JoinColumn[] inverseJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Lob {} + + + + + + + + + + + + public enum LockModeType { READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT, PESSIMISTIC_READ, PESSIMISTIC_WRITE, PESSIMISTIC_FORCE_INCREMENT, NONE}; + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKey { + String name() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyClass { + Class value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyColumn { + String name() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + } + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for a mapped superclass. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + If this is the case then the defaulting rules will be recursively + applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface MappedSuperclass{} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedAttributeNode { + String value(); + String subgraph() default ""; + String keySubgraph() default ""; + } + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedEntityGraph { + String name() default ""; + NamedAttributeNode[] attributeNodes() default {}; + boolean includeAllAttributes() default false; + NamedSubgraph[] subgraphs() default {}; + NamedSubGraph[] subclassSubgraphs() default {}; + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedNativeQuery { + String name(); + String query(); + QueryHint[] hints() default {}; + Class resultClass() default void.class; + String resultSetMapping() default ""; //named SqlResultSetMapping + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedQuery { + String name(); + String query(); + LockModeType lockMode() default NONE; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedStoredProcedureQuery { + String name(); + String procedureName(); + StoredProcedureParameter[] parameters() default {}; + Class[] resultClasses() default {}; + String[] resultSetMappings() default{}; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedSubgraph { + String name(); + Class type() default void.class; + NamedAttributeNode[] attributeNodes(); + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + String mappedBy() default ""; + boolean orphanRemoval() default false; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderBy { + String value() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderColumn { + String name() default ""; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + + + public enum ParameterMode { IN, INOUT, OUT, REF_CURSOR}; + + + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostLoad {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostPersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostUpdate {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PrePersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreUpdate {} + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface PrimaryKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface QueryHint { + String name(); + String value(); + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SecondaryTable { + String name(); + String catalog() default ""; + String schema() default ""; + PrimaryKeyJoinColumn[] pkJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface SequenceGenerator { + String name(); + String sequenceName() default ""; + String catalog() default ""; + String schema() default ""; + int initialValue() default 1; + int allocationSize() default 50; + } + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SqlResultSetMapping { + String name(); + EntityResult[] entities() default {}; + ConstructorResult[] classes() default{}; + ColumnResult[] columns() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface StoredProcedureParameter { + String name() default ""; + ParameterMode mode() default ParameterMode.IN; + Class type(); + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Table { + String name() default ""; + String catalog() default ""; + String schema() default ""; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface TableGenerator { + String name(); + String table() default ""; + String catalog() default ""; + String schema() default ""; + String pkColumnName() default ""; + String valueColumnName() default ""; + String pkColumnValue() default ""; + int initialValue() default 0; + int allocationSize() default 50; + UniqueConstraint[] uniqueConstraints() default {}; + Indexes[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Temporal { + TemporalType value(); + } + + + + + + + + + + + + + public enum TemporalType { + DATE, // java.sql.Date + TIME, // java.sql.Time + TIMESTAMP // java.sql.Timestamp + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Transient {} + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface UniqueConstraint { + String name() default ""; + String[] columnNames(); + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Version {} + + + + + + + + + + + + diff --git a/hibernate-entitymanager/src/main/resources/org/hibernate/jpa/persistence_2_1.xsd b/hibernate-entitymanager/src/main/resources/org/hibernate/jpa/persistence_2_1.xsd new file mode 100644 index 0000000000..d1ff0a90b2 --- /dev/null +++ b/hibernate-entitymanager/src/main/resources/org/hibernate/jpa/persistence_2_1.xsd @@ -0,0 +1,335 @@ + + + + + + + @(#)persistence_2_1.xsd 2.1 February 8, 2013 + + + + + + ... + + + ]]> + + + + + + + + + + + + + + + + + + + + + + Configuration of a persistence unit. + + + + + + + + + + + + Description of this persistence unit. + + + + + + + + + + + + Provider class that supplies EntityManagers for this + persistence unit. + + + + + + + + + + + + The container-specific name of the JTA datasource to use. + + + + + + + + + + + + The container-specific name of a non-JTA datasource to use. + + + + + + + + + + + + File containing mapping information. Loaded as a resource + by the persistence provider. + + + + + + + + + + + + Jar file that is to be scanned for managed classes. + + + + + + + + + + + + Managed class to be included in the persistence unit and + to scan for annotations. It should be annotated + with either @Entity, @Embeddable or @MappedSuperclass. + + + + + + + + + + + + When set to true then only listed classes and jars will + be scanned for persistent classes, otherwise the + enclosing jar or directory will also be scanned. + Not applicable to Java SE persistence units. + + + + + + + + + + + + Defines whether caching is enabled for the + persistence unit if caching is supported by the + persistence provider. When set to ALL, all entities + will be cached. When set to NONE, no entities will + be cached. When set to ENABLE_SELECTIVE, only entities + specified as cacheable will be cached. When set to + DISABLE_SELECTIVE, entities specified as not cacheable + will not be cached. When not specified or when set to + UNSPECIFIED, provider defaults may apply. + + + + + + + + + + + + The validation mode to be used for the persistence unit. + + + + + + + + + + + + + A list of standard and vendor-specific properties + and hints. + + + + + + + + + A name-value pair. + + + + + + + + + + + + + + + + + + + + Name used in code to reference this persistence unit. + + + + + + + + + + + + Type of transactions used by EntityManagers from this + persistence unit. + + + + + + + + + + + + + + + + + + + public enum PersistenceUnitTransactionType {JTA, RESOURCE_LOCAL}; + + + + + + + + + + + + + + + + public enum SharedCacheMode { ALL, NONE, ENABLE_SELECTIVE, DISABLE_SELECTIVE, UNSPECIFIED}; + + + + + + + + + + + + + + + + + + + public enum ValidationMode { AUTO, CALLBACK, NONE}; + + + + + + + + + + + diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/callbacks/CallbackAndDirtyTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/callbacks/CallbackAndDirtyTest.java index 4b2d9c5f76..2e171c32d5 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/callbacks/CallbackAndDirtyTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/callbacks/CallbackAndDirtyTest.java @@ -97,7 +97,7 @@ public class CallbackAndDirtyTest extends BaseEntityManagerFunctionalTestCase { manager.getTransaction().begin(); mark = manager.find( Employee.class, new Long( ids[0] ) ); - joe = (Customer) manager.find( Customer.class, new Long( ids[1] ) ); + joe = manager.find( Customer.class, new Long( ids[1] ) ); yomomma = manager.find( Person.class, new Long( ids[2] ) ); mark.setZip( "30306" ); diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/FetchTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/FetchTest.java index 3a4ed84aaf..a49c644281 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/FetchTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/cascade/FetchTest.java @@ -163,74 +163,6 @@ public class FetchTest extends BaseEntityManagerFunctionalTestCase { em.close(); } - @Test - public void testPerfCascadeAndFetchEntity() throws Exception { - EntityManager em = getOrCreateEntityManager(); - //init data - em.getTransaction().begin(); - int loop = 50; - for ( int i = 0; i < loop ; i++ ) { - Troop disney = new Troop(); - disney.setName( "Disney" ); - Soldier mickey = new Soldier(); - mickey.setName( "Mickey" ); - disney.addSoldier( mickey ); - em.persist( disney ); - } - em.getTransaction().commit(); - em.close(); - - //Warm up loop - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - for ( int i = 0; i < loop ; i++ ) { - //Soldier soldier = em.find( Soldier.class, new Integer(i) ); - Troop troop = em.find( Troop.class, new Integer( i ) ); - //( ( HibernateEntityManager ) em ).getSession().evict(soldier); - } - long l11 = System.currentTimeMillis(); - Query query = em.createQuery( "SELECT count(t) FROM Soldier t" ); - query.getSingleResult(); - long l2 = System.currentTimeMillis(); - System.out.println( "Query1 " + ( l2 - l11 ) ); - em.getTransaction().commit(); - em.close(); - - //do not evict - for ( int j = 0; j < 10 ; j++ ) { - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - for ( int i = 0; i < loop ; i++ ) { - Troop troop = em.find( Troop.class, new Integer( i ) ); - ( (HibernateEntityManager) em ).getSession().evict( troop ); - } - l11 = System.currentTimeMillis(); - query = em.createQuery( "SELECT count(t) FROM Soldier t" ); - query.getSingleResult(); - l2 = System.currentTimeMillis(); - System.out.println( "Query " + ( l2 - l11 ) ); - em.getTransaction().commit(); - em.close(); - - //evict - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - for ( int i = 0; i < loop ; i++ ) { - //Soldier soldier = em.find( Soldier.class, new Integer(i) ); - Troop troop = em.find( Troop.class, new Integer( i ) ); - - //( ( HibernateEntityManager ) em ).getSession().evict(troop); - } - l11 = System.currentTimeMillis(); - query = em.createQuery( "SELECT count(t) FROM Soldier t" ); - query.getSingleResult(); - l2 = System.currentTimeMillis(); - System.out.println( "Query " + ( l2 - l11 ) ); - em.getTransaction().commit(); - } - em.close(); - } - @Override public Class[] getAnnotatedClasses() { return new Class[]{ diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/basic/ExpressionsTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/basic/ExpressionsTest.java index bf2629d4f2..81632e2661 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/basic/ExpressionsTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/basic/ExpressionsTest.java @@ -40,11 +40,14 @@ import org.junit.Before; import org.junit.Test; import org.hibernate.Query; +import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; import org.hibernate.jpa.test.metamodel.Phone; import org.hibernate.jpa.test.metamodel.Product; import org.hibernate.jpa.test.metamodel.Product_; import org.hibernate.internal.AbstractQueryImpl; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; import static org.junit.Assert.assertEquals; @@ -97,6 +100,21 @@ public class ExpressionsTest extends AbstractMetamodelSpecificTest { em.close(); } + @Test + @TestForIssue( jiraKey = "HHH-6876" ) + @RequiresDialect( H2Dialect.class ) + public void testEmptyInList() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + CriteriaQuery criteria = builder.createQuery( Product.class ); + Root from = criteria.from( Product.class ); + criteria.where( from.get( Product_.partNumber ).in() ); // empty IN list + List result = em.createQuery( criteria ).getResultList(); + assertEquals( 0, result.size() ); + em.getTransaction().commit(); + em.close(); + } + @Test public void testEmptyConjunctionIsTrue() { EntityManager em = getOrCreateEntityManager(); diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/graphs/BasicEntityGraphTests.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/graphs/BasicEntityGraphTests.java new file mode 100644 index 0000000000..0f9fd767c6 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/graphs/BasicEntityGraphTests.java @@ -0,0 +1,126 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.test.graphs; + +import javax.persistence.AttributeNode; +import javax.persistence.Entity; +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Subgraph; + +import java.util.Set; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Steve Ebersole + */ +public class BasicEntityGraphTests extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Entity1.class }; + } + + @Test + public void testBasicGraphBuilding() { + EntityManager em = getOrCreateEntityManager(); + EntityGraph graphRoot = em.createEntityGraph( Entity1.class ); + assertNull( graphRoot.getName() ); + assertEquals( 0, graphRoot.getAttributeNodes().size() ); + } + + @Test + public void testBasicSubgraphBuilding() { + EntityManager em = getOrCreateEntityManager(); + EntityGraph graphRoot = em.createEntityGraph( Entity1.class ); + Subgraph parentGraph = graphRoot.addSubgraph( "parent" ); + Subgraph childGraph = graphRoot.addSubgraph( "children" ); + + assertNull( graphRoot.getName() ); + assertEquals( 2, graphRoot.getAttributeNodes().size() ); + assertTrue( + graphRoot.getAttributeNodes().get( 0 ).getSubgraphs().containsValue( parentGraph ) + || graphRoot.getAttributeNodes().get( 0 ).getSubgraphs().containsValue( childGraph ) + ); + assertTrue( + graphRoot.getAttributeNodes().get( 1 ).getSubgraphs().containsValue( parentGraph ) + || graphRoot.getAttributeNodes().get( 1 ).getSubgraphs().containsValue( childGraph ) + ); + } + + @Test + public void testBasicGraphImmutability() { + EntityManager em = getOrCreateEntityManager(); + EntityGraph graphRoot = em.createEntityGraph( Entity1.class ); + graphRoot.addSubgraph( "parent" ); + graphRoot.addSubgraph( "children" ); + + em.getEntityManagerFactory().addNamedEntityGraph( "immutable", graphRoot ); + + graphRoot = em.getEntityGraph( "immutable" ); + + assertEquals( "immutable", graphRoot.getName() ); + assertEquals( 2, graphRoot.getAttributeNodes().size() ); + try { + graphRoot.addAttributeNodes( "parent" ); + fail( "Should have failed" ); + } + catch (IllegalStateException ignore) { + // expected outcome + } + + for ( AttributeNode attrNode : graphRoot.getAttributeNodes() ) { + assertEquals( 1, attrNode.getSubgraphs().size() ); + Subgraph subgraph = (Subgraph) attrNode.getSubgraphs().values().iterator().next(); + try { + graphRoot.addAttributeNodes( "parent" ); + fail( "Should have failed" ); + } + catch (IllegalStateException ignore) { + // expected outcome + } + } + } + + @Entity( name = "Entity1" ) + public static class Entity1 { + @Id + public Integer id; + public String name; + @ManyToOne + public Entity1 parent; + @OneToMany( mappedBy = "parent" ) + public Set children; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTest.java index fecfce0cfb..d6b4b25868 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTest.java @@ -25,6 +25,7 @@ package org.hibernate.jpa.test.lock; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.Oracle10gDialect; +import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.ejb.AvailableSettings; @@ -328,8 +329,14 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { } @Test - // ASE15.5 will generate select...holdlock and fail at this test, but ASE15.7 passes it. Skip it for ASE15.5 only. - @SkipForDialect(value = { HSQLDialect.class, SybaseASE15Dialect.class },strictMatching = true, jiraKey = "HHH-6820") + + @SkipForDialects({ + @SkipForDialect(HSQLDialect.class), + // ASE15.5 will generate select...holdlock and fail at this test, but ASE15.7 passes it. Skip it for ASE15.5 + // only. + @SkipForDialect(value = { SybaseASE15Dialect.class }, strictMatching = true, jiraKey = "HHH-6820"), + // TODO Remove once HHH-8001 is fixed. + @SkipForDialect(value = { Oracle8iDialect.class }, jiraKey = "HHH-8001") }) public void testContendedPessimisticLock() throws Exception { final EntityManager em = getOrCreateEntityManager(); final EntityManager isolatedEntityManager = createIsolatedEntityManager(); @@ -719,6 +726,7 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @Test @RequiresDialect( Oracle10gDialect.class ) @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) + @FailureExpected( jiraKey = "HHH-8001" ) public void testQueryTimeout() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -812,6 +820,7 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @Test @RequiresDialect( Oracle10gDialect.class ) @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) + @FailureExpected( jiraKey = "HHH-8001" ) public void testQueryTimeoutEMProps() throws Exception { EntityManager em = getOrCreateEntityManager(); Map queryTimeoutProps = new HashMap(); diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTimeoutPropertyTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTimeoutPropertyTest.java index 1e21ae3412..385ff60550 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTimeoutPropertyTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/lock/LockTimeoutPropertyTest.java @@ -35,7 +35,7 @@ public class LockTimeoutPropertyTest extends BaseEntityManagerFunctionalTestCase em.getTransaction().begin(); Query query = em.createNamedQuery( "getAll" ); query.setLockMode( LockModeType.PESSIMISTIC_READ ); - int timeout = ((QueryImpl)(((org.hibernate.jpa.internal.QueryImpl)query).getHibernateQuery())).getLockOptions().getTimeOut(); + int timeout = ((org.hibernate.jpa.internal.QueryImpl)query).getHibernateQuery().getLockOptions().getTimeOut(); assertEquals( 3000, timeout ); } @@ -50,14 +50,14 @@ public class LockTimeoutPropertyTest extends BaseEntityManagerFunctionalTestCase int timeout = Integer.valueOf( em.getProperties().get( AvailableSettings.LOCK_TIMEOUT ).toString() ); assertEquals( 2000, timeout); org.hibernate.jpa.internal.QueryImpl q = (org.hibernate.jpa.internal.QueryImpl) em.createQuery( "select u from UnversionedLock u" ); - timeout = ((QueryImpl)q.getHibernateQuery()).getLockOptions().getTimeOut(); + timeout = q.getHibernateQuery().getLockOptions().getTimeOut(); assertEquals( 2000, timeout ); Query query = em.createQuery( "select u from UnversionedLock u" ); query.setLockMode(LockModeType.PESSIMISTIC_WRITE); query.setHint( AvailableSettings.LOCK_TIMEOUT, 3000 ); q = (org.hibernate.jpa.internal.QueryImpl)query; - timeout = ((QueryImpl)q.getHibernateQuery()).getLockOptions().getTimeOut(); + timeout = q.getHibernateQuery().getLockOptions().getTimeOut(); assertEquals( 3000, timeout ); em.getTransaction().rollback(); em.close(); diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/Book.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/Book.java index ad43fbd9ab..6ee7a5dda6 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/Book.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/Book.java @@ -18,6 +18,13 @@ public class Book { @Version public Integer version; + + public Book() {} + + public Book(String name, Integer version) { + this.name = name; + this.version = version; + } public Integer getId() { return id; diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java index bf152be86b..eca6de48cb 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/transaction/TransactionJoiningTest.java @@ -23,25 +23,29 @@ */ package org.hibernate.jpa.test.transaction; -import javax.persistence.EntityManager; -import javax.transaction.Synchronization; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import javax.persistence.EntityManager; +import javax.transaction.Status; +import javax.transaction.Synchronization; import org.hibernate.Session; import org.hibernate.Transaction; -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.transaction.internal.jta.CMTTransaction; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; +import org.hibernate.internal.SessionImpl; import org.hibernate.jpa.AvailableSettings; - -import org.junit.Test; - +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.jta.TestingJtaBootstrap; import org.hibernate.testing.jta.TestingJtaPlatformImpl; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.Test; /** * Largely a copy of {@link org.hibernate.test.jpa.txn.TransactionJoiningTest} @@ -138,4 +142,49 @@ public class TransactionJoiningTest extends BaseEntityManagerFunctionalTestCase ); TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit(); } + + /** + * In certain JTA environments (JBossTM, etc.), a background thread (reaper) + * can rollback a transaction if it times out. These timeouts are rare and + * typically come from server failures. However, we need to handle the + * multi-threaded nature of the transaction afterCompletion action. + * Emulate a timeout with a simple afterCompletion call in a thread. + * See HHH-7910 + */ + @Test + @TestForIssue(jiraKey="HHH-7910") + public void testMultiThreadTransactionTimeout() throws Exception { + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin(); + + EntityManager em = entityManagerFactory().createEntityManager(); + final SessionImpl sImpl = em.unwrap( SessionImpl.class ); + + final CountDownLatch latch = new CountDownLatch(1); + + Thread thread = new Thread() { + public void run() { + sImpl.getTransactionCoordinator().getSynchronizationCallbackCoordinator().afterCompletion( Status.STATUS_ROLLEDBACK ); + latch.countDown(); + } + }; + thread.start(); + + latch.await(); + + em.persist( new Book( "The Book of Foo", 1 ) ); + + // Ensure that the session was cleared by the background thread. + assertEquals( "The background thread did not clear the session as expected!", + 0, em.createQuery( "from Book" ).getResultList().size() ); + + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit(); + em.close(); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } } diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/util/Article.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/util/Article.java new file mode 100644 index 0000000000..19d28da6da --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/util/Article.java @@ -0,0 +1,44 @@ +package org.hibernate.jpa.test.util; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +public class Article implements Serializable { + @Id + @GeneratedValue + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + private Author author; + + public Article() { + } + + public Article(Author author) { + this.author = author; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/util/GetIdentifierTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/util/GetIdentifierTest.java index dd52e838b6..b86979fbc3 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/util/GetIdentifierTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/util/GetIdentifierTest.java @@ -28,8 +28,11 @@ import javax.persistence.EntityManager; import org.junit.Test; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.testing.TestForIssue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * @author Emmanuel Bernard @@ -47,6 +50,36 @@ public class GetIdentifierTest extends BaseEntityManagerFunctionalTestCase { em.close(); } + @Test + @TestForIssue(jiraKey = "HHH-7561") + public void testProxyObject() { + EntityManager em = entityManagerFactory().createEntityManager(); + em.getTransaction().begin(); + Book book = new Book(); + em.persist( book ); + em.flush(); + em.clear(); // Clear persistence context to receive proxy object below. + Book proxy = em.getReference( Book.class, book.getId() ); + assertTrue( proxy instanceof HibernateProxy ); + assertEquals( book.getId(), em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier( proxy ) ); + em.getTransaction().rollback(); + em.close(); + + em = entityManagerFactory().createEntityManager(); + em.getTransaction().begin(); + Author author = new Author(); + Article article = new Article( author ); + em.persist( author ); + em.persist( article ); + em.flush(); + em.clear(); // Clear persistence context to receive proxy relation below. + article = em.find( Article.class, article.getId() ); + assertTrue( article.getAuthor() instanceof HibernateProxy ); + assertEquals( author.getId(), em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier( article.getAuthor() ) ); + em.getTransaction().rollback(); + em.close(); + } + @Test public void testEmbeddedId() { EntityManager em = entityManagerFactory().createEntityManager(); @@ -86,7 +119,8 @@ public class GetIdentifierTest extends BaseEntityManagerFunctionalTestCase { Book.class, Umbrella.class, Sickness.class, - Author.class + Author.class, + Article.class }; } } diff --git a/hibernate-envers/hibernate-envers.gradle b/hibernate-envers/hibernate-envers.gradle index 28fa978cac..eb2acb42fd 100644 --- a/hibernate-envers/hibernate-envers.gradle +++ b/hibernate-envers/hibernate-envers.gradle @@ -48,3 +48,9 @@ task generateJpaMetamodelClasses(type: Compile) { } compileJava.dependsOn generateJpaMetamodelClasses +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM Envers' + instruction 'Bundle-SymbolicName', 'org.hibernate.envers' + } +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/AuditConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/AuditConfiguration.java index 1316da28d3..eb705e47cc 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/AuditConfiguration.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/AuditConfiguration.java @@ -39,6 +39,7 @@ import org.hibernate.envers.strategy.AuditStrategy; import org.hibernate.envers.strategy.ValidityAuditStrategy; import org.hibernate.envers.synchronization.AuditProcessManager; import org.hibernate.envers.tools.reflection.ReflectionTools; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.Getter; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; @@ -126,12 +127,10 @@ public class AuditConfiguration { auditStrategyClass = classLoaderService.classForName( auditEntCfg.getAuditStrategyName() ); } else { - auditStrategyClass = Thread.currentThread() - .getContextClassLoader() - .loadClass( auditEntCfg.getAuditStrategyName() ); + auditStrategyClass = ReflectHelper.classForName( auditEntCfg.getAuditStrategyName() ); } - strategy = (AuditStrategy) auditStrategyClass.newInstance(); + strategy = (AuditStrategy) ReflectHelper.getDefaultConstructor(auditStrategyClass).newInstance(); } catch ( Exception e ) { throw new MappingException( diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/GlobalConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/GlobalConfiguration.java index a7d0fd7bee..34f3d51f3f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/GlobalConfiguration.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/GlobalConfiguration.java @@ -22,12 +22,13 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.configuration; +import static org.hibernate.envers.tools.Tools.getProperty; + import java.util.Properties; import org.hibernate.MappingException; import org.hibernate.envers.RevisionListener; - -import static org.hibernate.envers.tools.Tools.getProperty; +import org.hibernate.internal.util.ReflectHelper; /** * @author Adam Warski (adam at warski dot org) @@ -134,7 +135,7 @@ public class GlobalConfiguration { String revisionListenerClassName = properties.getProperty("org.hibernate.envers.revision_listener", null); if (revisionListenerClassName != null) { try { - revisionListenerClass = (Class) Thread.currentThread().getContextClassLoader().loadClass(revisionListenerClassName); + revisionListenerClass = (Class) ReflectHelper.classForName(revisionListenerClassName); } catch (ClassNotFoundException e) { throw new MappingException("Revision listener class not found: " + revisionListenerClassName + ".", e); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/PersistentClassGraphDefiner.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/PersistentClassGraphDefiner.java index 632e415348..fc116123df 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/PersistentClassGraphDefiner.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/PersistentClassGraphDefiner.java @@ -71,6 +71,6 @@ public class PersistentClassGraphDefiner implements GraphDefiner getValues() { - return Tools.iteratorToList((Iterator) cfg.getClassMappings()); + return Tools.iteratorToList( cfg.getClassMappings() ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java index c3cb4f3c6b..8695b69503 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java @@ -140,13 +140,13 @@ public final class AuditMetadataGenerator { any_mapping.add(cloneAndSetupRevisionInfoRelationMapping()); } - void addRevisionType(Element any_mapping) { + void addRevisionType(Element any_mapping, Element any_mapping_end) { Element revTypeProperty = MetadataTools.addProperty(any_mapping, verEntCfg.getRevisionTypePropName(), verEntCfg.getRevisionTypePropType(), true, false); revTypeProperty.addAttribute("type", "org.hibernate.envers.entities.RevisionTypeType"); // Adding the end revision, if appropriate - addEndRevision(any_mapping); + addEndRevision(any_mapping_end); } private void addEndRevision(Element any_mapping ) { @@ -377,7 +377,7 @@ public final class AuditMetadataGenerator { class_mapping.add((Element) idMapper.getXmlMapping().clone()); // Adding the "revision type" property - addRevisionType(class_mapping); + addRevisionType(class_mapping, class_mapping); return Triple.make(class_mapping, propertyMapper, null); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java index 907212bb82..e10964cff2 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java @@ -38,13 +38,18 @@ import org.dom4j.Element; import org.jboss.logging.Logger; import org.hibernate.MappingException; +import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.envers.ModificationStore; import org.hibernate.envers.RelationTargetAuditMode; +import org.hibernate.envers.configuration.metadata.reader.AuditedPropertiesReader; +import org.hibernate.envers.configuration.metadata.reader.ComponentAuditedPropertiesReader; +import org.hibernate.envers.configuration.metadata.reader.ComponentAuditingData; import org.hibernate.envers.configuration.metadata.reader.PropertyAuditingData; import org.hibernate.envers.entities.EntityConfiguration; import org.hibernate.envers.entities.IdMappingData; import org.hibernate.envers.entities.PropertyData; import org.hibernate.envers.entities.mapper.CompositeMapperBuilder; +import org.hibernate.envers.entities.mapper.MultiPropertyMapper; import org.hibernate.envers.entities.mapper.PropertyMapper; import org.hibernate.envers.entities.mapper.SinglePropertyMapper; import org.hibernate.envers.entities.mapper.id.IdMapper; @@ -58,6 +63,7 @@ import org.hibernate.envers.entities.mapper.relation.SortedMapCollectionMapper; import org.hibernate.envers.entities.mapper.relation.SortedSetCollectionMapper; import org.hibernate.envers.entities.mapper.relation.ToOneIdMapper; import org.hibernate.envers.entities.mapper.relation.component.MiddleDummyComponentMapper; +import org.hibernate.envers.entities.mapper.relation.component.MiddleEmbeddableComponentMapper; import org.hibernate.envers.entities.mapper.relation.component.MiddleMapKeyIdComponentMapper; import org.hibernate.envers.entities.mapper.relation.component.MiddleMapKeyPropertyComponentMapper; import org.hibernate.envers.entities.mapper.relation.component.MiddleRelatedComponentMapper; @@ -75,6 +81,7 @@ import org.hibernate.envers.tools.MappingTools; import org.hibernate.envers.tools.StringTools; import org.hibernate.envers.tools.Tools; import org.hibernate.mapping.Collection; +import org.hibernate.mapping.Component; import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.OneToMany; @@ -83,6 +90,7 @@ import org.hibernate.mapping.Property; import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; import org.hibernate.type.BagType; +import org.hibernate.type.ComponentType; import org.hibernate.type.ListType; import org.hibernate.type.ManyToOneType; import org.hibernate.type.MapType; @@ -203,7 +211,7 @@ public final class CollectionMetadataGenerator { // Generating the query generator - it should read directly from the related entity. RelationQueryGenerator queryGenerator = new OneAuditEntityQueryGenerator(mainGenerator.getGlobalCfg(), mainGenerator.getVerEntCfg(), mainGenerator.getAuditStrategy(), - referencingIdData, referencedEntityName, referencedIdData); + referencingIdData, referencedEntityName, referencedIdData, isEmbeddableElementType()); // Creating common mapper data. CommonCollectionMapperData commonCollectionMapperData = new CommonCollectionMapperData( @@ -351,7 +359,7 @@ public final class CollectionMetadataGenerator { // a query generator to read the raw data collection from the middle table. QueryGeneratorBuilder queryGeneratorBuilder = new QueryGeneratorBuilder(mainGenerator.getGlobalCfg(), mainGenerator.getVerEntCfg(), mainGenerator.getAuditStrategy(), referencingIdData, - auditMiddleEntityName); + auditMiddleEntityName, isEmbeddableElementType()); // Adding the XML mapping for the referencing entity, if the relation isn't inverse. if (middleEntityXml != null) { @@ -463,6 +471,41 @@ public final class CollectionMetadataGenerator { return new MiddleComponentData(new MiddleRelatedComponentMapper(referencedIdData), queryGeneratorBuilder.getCurrentIndex()); + } else if ( type instanceof ComponentType ) { + // Collection of embeddable elements. + final Component component = (Component) value; + final MiddleEmbeddableComponentMapper componentMapper = new MiddleEmbeddableComponentMapper( new MultiPropertyMapper(), component.getComponentClassName() ); + + final Element parentXmlMapping = xmlMapping.getParent(); + final ComponentAuditingData auditData = new ComponentAuditingData(); + final ReflectionManager reflectionManager = mainGenerator.getCfg().getReflectionManager(); + + new ComponentAuditedPropertiesReader( + ModificationStore.FULL, + new AuditedPropertiesReader.ComponentPropertiesSource( reflectionManager, component ), + auditData, mainGenerator.getGlobalCfg(), reflectionManager, "" + ).read(); + + // Emulating first pass. + for ( String auditedPropertyName : auditData.getPropertyNames() ) { + PropertyAuditingData nestedAuditingData = auditData.getPropertyAuditingData( auditedPropertyName ); + mainGenerator.addValue( + parentXmlMapping, component.getProperty( auditedPropertyName ).getValue(), componentMapper, + prefix, xmlMappingData, nestedAuditingData, true, true, true + ); + } + + // Emulating second pass so that the relations can be mapped too. + for ( String auditedPropertyName : auditData.getPropertyNames() ) { + PropertyAuditingData nestedAuditingData = auditData.getPropertyAuditingData( auditedPropertyName ); + mainGenerator.addValue( + parentXmlMapping, component.getProperty( auditedPropertyName ).getValue(), + componentMapper, referencingEntityName, xmlMappingData, nestedAuditingData, + true, false, true + ); + } + + return new MiddleComponentData( componentMapper, 0 ); } else { // Last but one parameter: collection components are always insertable boolean mapped = mainGenerator.getBasicMetadataGenerator().addBasic(xmlMapping, @@ -483,33 +526,36 @@ public final class CollectionMetadataGenerator { private void addMapper(CommonCollectionMapperData commonCollectionMapperData, MiddleComponentData elementComponentData, MiddleComponentData indexComponentData) { Type type = propertyValue.getType(); + boolean embeddableElementType = isEmbeddableElementType(); if (type instanceof SortedSetType) { currentMapper.addComposite(propertyAuditingData.getPropertyData(), new SortedSetCollectionMapper(commonCollectionMapperData, - TreeSet.class, SortedSetProxy.class, elementComponentData, propertyValue.getComparator())); + TreeSet.class, SortedSetProxy.class, elementComponentData, propertyValue.getComparator(), + embeddableElementType)); } else if (type instanceof SetType) { currentMapper.addComposite(propertyAuditingData.getPropertyData(), new BasicCollectionMapper(commonCollectionMapperData, - HashSet.class, SetProxy.class, elementComponentData)); + HashSet.class, SetProxy.class, elementComponentData, embeddableElementType)); } else if (type instanceof SortedMapType) { // Indexed collection, so indexComponentData is not null. currentMapper.addComposite(propertyAuditingData.getPropertyData(), new SortedMapCollectionMapper(commonCollectionMapperData, - TreeMap.class, SortedMapProxy.class, elementComponentData, indexComponentData, propertyValue.getComparator())); + TreeMap.class, SortedMapProxy.class, elementComponentData, indexComponentData, propertyValue.getComparator(), + embeddableElementType)); } else if (type instanceof MapType) { // Indexed collection, so indexComponentData is not null. currentMapper.addComposite(propertyAuditingData.getPropertyData(), new MapCollectionMapper(commonCollectionMapperData, - HashMap.class, MapProxy.class, elementComponentData, indexComponentData)); + HashMap.class, MapProxy.class, elementComponentData, indexComponentData, embeddableElementType)); } else if (type instanceof BagType) { currentMapper.addComposite(propertyAuditingData.getPropertyData(), new BasicCollectionMapper(commonCollectionMapperData, - ArrayList.class, ListProxy.class, elementComponentData)); + ArrayList.class, ListProxy.class, elementComponentData, embeddableElementType)); } else if (type instanceof ListType) { // Indexed collection, so indexComponentData is not null. currentMapper.addComposite(propertyAuditingData.getPropertyData(), new ListCollectionMapper(commonCollectionMapperData, - elementComponentData, indexComponentData)); + elementComponentData, indexComponentData, embeddableElementType)); } else { mainGenerator.throwUnsupportedTypeException(type, referencingEntityName, propertyName); } @@ -546,12 +592,19 @@ public final class CollectionMetadataGenerator { mainGenerator.addRevisionInfoRelation(middleEntityXmlId); // Adding the revision type property to the entity xml. - mainGenerator.addRevisionType(middleEntityXml); + mainGenerator.addRevisionType(isEmbeddableElementType() ? middleEntityXmlId : middleEntityXml, middleEntityXml); // All other properties should also be part of the primary key of the middle entity. return middleEntityXmlId; } + /** + * Checks if the collection element is of {@link ComponentType} type. + */ + private boolean isEmbeddableElementType() { + return propertyValue.getElement().getType() instanceof ComponentType; + } + private String getMappedBy(Collection collectionValue) { PersistentClass referencedClass = null; if (collectionValue.getElement() instanceof OneToMany) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java index 6735e3aa71..bc210a0af9 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java @@ -49,15 +49,17 @@ public final class QueryGeneratorBuilder { private final MiddleIdData referencingIdData; private final String auditMiddleEntityName; private final List idDatas; + private final boolean revisionTypeInId; QueryGeneratorBuilder(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, - AuditStrategy auditStrategy, - MiddleIdData referencingIdData, String auditMiddleEntityName) { + AuditStrategy auditStrategy, MiddleIdData referencingIdData, String auditMiddleEntityName, + boolean revisionTypeInId) { this.globalCfg = globalCfg; this.verEntCfg = verEntCfg; this.auditStrategy = auditStrategy; this.referencingIdData = referencingIdData; this.auditMiddleEntityName = auditMiddleEntityName; + this.revisionTypeInId = revisionTypeInId; idDatas = new ArrayList(); } @@ -69,14 +71,14 @@ public final class QueryGeneratorBuilder { RelationQueryGenerator build(MiddleComponentData... componentDatas) { if (idDatas.size() == 0) { return new OneEntityQueryGenerator(verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData, - componentDatas); + revisionTypeInId, componentDatas); } else if (idDatas.size() == 1) { if (idDatas.get(0).isAudited()) { return new TwoEntityQueryGenerator(globalCfg, verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData, - idDatas.get(0), componentDatas); + idDatas.get(0), revisionTypeInId, componentDatas); } else { return new TwoEntityOneAuditedQueryGenerator(verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData, - idDatas.get(0), componentDatas); + idDatas.get(0), revisionTypeInId, componentDatas); } } else if (idDatas.size() == 2) { // All entities must be audited. @@ -85,7 +87,7 @@ public final class QueryGeneratorBuilder { } return new ThreeEntityQueryGenerator(globalCfg, verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData, - idDatas.get(0), idDatas.get(1), componentDatas); + idDatas.get(0), idDatas.get(1), revisionTypeInId, componentDatas); } else { throw new IllegalStateException("Illegal number of related entities."); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java index b9067cec8e..96c1e21afd 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java @@ -212,7 +212,7 @@ public class AuditedPropertiesReader { private void readPersistentPropertiesAccess() { Iterator propertyIter = persistentPropertiesSource.getPropertyIterator(); while (propertyIter.hasNext()) { - Property property = (Property) propertyIter.next(); + Property property = propertyIter.next(); addPersistentProperty(property); if ("embedded".equals(property.getPropertyAccessorName()) && property.getName().equals(property.getNodeName())) { // If property name equals node name and embedded accessor type is used, processing component @@ -319,7 +319,7 @@ public class AuditedPropertiesReader { // Marking component properties as placed directly in class (not inside another component). componentData.setBeanName(null); - PersistentPropertiesSource componentPropertiesSource = new ComponentPropertiesSource((Component) propertyValue); + PersistentPropertiesSource componentPropertiesSource = new ComponentPropertiesSource( reflectionManager, propertyValue ); AuditedPropertiesReader audPropReader = new AuditedPropertiesReader( ModificationStore.FULL, componentPropertiesSource, componentData, globalCfg, reflectionManager, propertyNamePrefix + MappingTools.createComponentPrefix(embeddedName) @@ -338,7 +338,8 @@ public class AuditedPropertiesReader { allClassAudited); PersistentPropertiesSource componentPropertiesSource = new ComponentPropertiesSource( - (Component) propertyValue); + reflectionManager, propertyValue + ); ComponentAuditedPropertiesReader audPropReader = new ComponentAuditedPropertiesReader( ModificationStore.FULL, componentPropertiesSource, @@ -540,11 +541,11 @@ public class AuditedPropertiesReader { public Class annotationType() { return this.getClass(); } }; - private class ComponentPropertiesSource implements PersistentPropertiesSource { + public static class ComponentPropertiesSource implements PersistentPropertiesSource { private final XClass xclass; private final Component component; - private ComponentPropertiesSource(Component component) { + public ComponentPropertiesSource(ReflectionManager reflectionManager, Component component) { try { this.xclass = reflectionManager.classForName(component.getComponentClassName(), this.getClass()); } catch (ClassNotFoundException e) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ComponentAuditingData.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ComponentAuditingData.java index 104a7664a4..b6108477b5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ComponentAuditingData.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ComponentAuditingData.java @@ -24,6 +24,7 @@ */ package org.hibernate.envers.configuration.metadata.reader; import java.util.Map; +import java.util.Set; import static org.hibernate.envers.tools.Tools.newHashMap; @@ -53,5 +54,9 @@ public class ComponentAuditingData extends PropertyAuditingData implements Audit public boolean contains(String propertyName) { return properties.containsKey(propertyName); - } + } + + public Set getPropertyNames() { + return properties.keySet(); + } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java index a873531229..97e5a3606c 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java @@ -122,5 +122,5 @@ public class EntityConfiguration { */ public String getEntityClassName() { return entityClassName; - } + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityInstantiator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityInstantiator.java index b4e07432f2..b8758b9b74 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityInstantiator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityInstantiator.java @@ -25,11 +25,9 @@ package org.hibernate.envers.entities; import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.mapper.id.IdMapper; -import org.hibernate.envers.entities.mapper.id.MultipleIdMapper; import org.hibernate.envers.entities.mapper.relation.lazy.ToOneDelegateSessionImplementor; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.reader.AuditReaderImplementor; -import org.hibernate.envers.tools.reflection.ReflectionTools; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -97,7 +95,7 @@ public class EntityInstantiator { entCfg = verCfg.getEntCfg().getNotVersionEntityConfiguration(entityName); } - Class cls = ReflectionTools.loadClass(entCfg.getEntityClassName()); + Class cls = ReflectHelper.classForName(entCfg.getEntityClassName()); ret = ReflectHelper.getDefaultConstructor(cls).newInstance(); } catch (Exception e) { throw new AuditException(e); @@ -128,7 +126,14 @@ public class EntityInstantiator { final Serializable entityId = initializer.getIdentifier(); if (verCfg.getEntCfg().isVersioned(entityName)) { final String entityClassName = verCfg.getEntCfg().get(entityName).getEntityClassName(); - final ToOneDelegateSessionImplementor delegate = new ToOneDelegateSessionImplementor(versionsReader, ReflectionTools.loadClass(entityClassName), entityId, revision, verCfg); + Class entityClass; + try { + entityClass = ReflectHelper.classForName(entityClassName); + } + catch ( ClassNotFoundException e ) { + throw new AuditException( e ); + } + final ToOneDelegateSessionImplementor delegate = new ToOneDelegateSessionImplementor(versionsReader, entityClass, entityId, revision, verCfg); originalId.put(key, versionsReader.getSessionImplementor().getFactory().getEntityPersister(entityName).createProxy(entityId, delegate)); } @@ -142,4 +147,12 @@ public class EntityInstantiator { addTo.add(createInstanceFromVersionsEntity(entityName, versionsEntity, revision)); } } + + public AuditConfiguration getAuditConfiguration() { + return verCfg; + } + + public AuditReaderImplementor getAuditReaderImplementor() { + return versionsReader; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/ComponentPropertyMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/ComponentPropertyMapper.java index dd2889cd62..045adf7813 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/ComponentPropertyMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/ComponentPropertyMapper.java @@ -121,7 +121,7 @@ public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperB // set the component try { Object subObj = ReflectHelper.getDefaultConstructor( - Thread.currentThread().getContextClassLoader().loadClass(componentClassName)).newInstance(); + ReflectHelper.classForName(componentClassName)).newInstance(); setter.set(obj, subObj, null); delegate.mapToEntityFromMap(verCfg, subObj, data, primaryKey, versionsReader, revision); } catch (Exception e) { @@ -130,11 +130,13 @@ public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperB } } - public List mapCollectionChanges(String referencingPropertyName, - PersistentCollection newColl, - Serializable oldColl, - Serializable id) { - return delegate.mapCollectionChanges(referencingPropertyName, newColl, oldColl, id); + public List mapCollectionChanges(SessionImplementor session, String referencingPropertyName, + PersistentCollection newColl, + Serializable oldColl, Serializable id) { + return delegate.mapCollectionChanges(session, referencingPropertyName, newColl, oldColl, id); } + public Map getProperties() { + return delegate.getProperties(); + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/CompositeMapperBuilder.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/CompositeMapperBuilder.java index 6f1e9ba055..420c47fe90 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/CompositeMapperBuilder.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/CompositeMapperBuilder.java @@ -22,6 +22,9 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.entities.mapper; + +import java.util.Map; + import org.hibernate.envers.entities.PropertyData; /** @@ -30,4 +33,5 @@ import org.hibernate.envers.entities.PropertyData; public interface CompositeMapperBuilder extends SimpleMapperBuilder { public CompositeMapperBuilder addComponent(PropertyData propertyData, String componentClassName); public void addComposite(PropertyData propertyData, PropertyMapper propertyMapper); + public Map getProperties(); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/MultiPropertyMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/MultiPropertyMapper.java index 4dbfe3a272..018a4c90ed 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/MultiPropertyMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/MultiPropertyMapper.java @@ -171,14 +171,14 @@ public class MultiPropertyMapper implements ExtendedPropertyMapper { } } - public List mapCollectionChanges(String referencingPropertyName, - PersistentCollection newColl, - Serializable oldColl, - Serializable id) { + public List mapCollectionChanges(SessionImplementor session, + String referencingPropertyName, + PersistentCollection newColl, + Serializable oldColl, Serializable id) { Pair pair = getMapperAndDelegatePropName(referencingPropertyName); PropertyMapper mapper = pair.getFirst(); if (mapper != null) { - return mapper.mapCollectionChanges(pair.getSecond(), newColl, oldColl, id); + return mapper.mapCollectionChanges(session, pair.getSecond(), newColl, oldColl, id); } else { return null; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java index feaac520da..d2b1bfcfb5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java @@ -59,18 +59,18 @@ public interface PropertyMapper { AuditReaderImplementor versionsReader, Number revision); /** - * Maps collection changes + * Maps collection changes. + * @param session The current session. * @param referencingPropertyName Name of the field, which holds the collection in the entity. * @param newColl New collection, after updates. * @param oldColl Old collection, before updates. * @param id Id of the object owning the collection. * @return List of changes that need to be performed on the persistent store. */ - List mapCollectionChanges(String referencingPropertyName, + List mapCollectionChanges(SessionImplementor session, String referencingPropertyName, PersistentCollection newColl, Serializable oldColl, Serializable id); void mapModifiedFlagsToMapFromEntity(SessionImplementor session, Map data, Object newObj, Object oldObj); void mapModifiedFlagsToMapForCollectionChange(String collectionPropertyName, Map data); - } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java index 1ed998e084..fd20bec1fe 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java @@ -67,7 +67,7 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder boolean dbLogicallyDifferent = true; if ((session.getFactory().getDialect() instanceof Oracle8iDialect) && (newObj instanceof String || oldObj instanceof String)) { // Don't generate new revision when database replaces empty string with NULL during INSERT or UPDATE statements. - dbLogicallyDifferent = !(StringTools.isEmpty((String) newObj) && StringTools.isEmpty((String) oldObj)); + dbLogicallyDifferent = !(StringTools.isEmpty(newObj) && StringTools.isEmpty(oldObj)); } return dbLogicallyDifferent && !Tools.objectsEqual(newObj, oldObj); } @@ -115,10 +115,10 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder } } - public List mapCollectionChanges(String referencingPropertyName, + public List mapCollectionChanges(SessionImplementor sessionImplementor, + String referencingPropertyName, PersistentCollection newColl, - Serializable oldColl, - Serializable id) { + Serializable oldColl, Serializable id) { return null; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SubclassPropertyMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SubclassPropertyMapper.java index 581bf76f7b..a9a703f936 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SubclassPropertyMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/SubclassPropertyMapper.java @@ -23,6 +23,7 @@ */ package org.hibernate.envers.entities.mapper; import java.io.Serializable; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -78,15 +79,14 @@ public class SubclassPropertyMapper implements ExtendedPropertyMapper { main.mapToEntityFromMap(verCfg, obj, data, primaryKey, versionsReader, revision); } - public List mapCollectionChanges(String referencingPropertyName, - PersistentCollection newColl, - Serializable oldColl, - Serializable id) { + public List mapCollectionChanges(SessionImplementor session, String referencingPropertyName, + PersistentCollection newColl, + Serializable oldColl, Serializable id) { List parentCollectionChanges = parentMapper.mapCollectionChanges( - referencingPropertyName, newColl, oldColl, id); + session, referencingPropertyName, newColl, oldColl, id); List mainCollectionChanges = main.mapCollectionChanges( - referencingPropertyName, newColl, oldColl, id); + session, referencingPropertyName, newColl, oldColl, id); if (parentCollectionChanges == null) { return mainCollectionChanges; @@ -109,4 +109,11 @@ public class SubclassPropertyMapper implements ExtendedPropertyMapper { public void add(PropertyData propertyData) { main.add(propertyData); } + + public Map getProperties() { + final Map joinedProperties = new HashMap(); + joinedProperties.putAll(parentMapper.getProperties()); + joinedProperties.putAll(main.getProperties()); + return joinedProperties; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/AbstractCompositeIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/AbstractCompositeIdMapper.java index c993633117..da020efd9f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/AbstractCompositeIdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/AbstractCompositeIdMapper.java @@ -27,6 +27,7 @@ import java.util.Map; import org.hibernate.envers.entities.PropertyData; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.tools.Tools; +import org.hibernate.internal.util.ReflectHelper; /** * @author Adam Warski (adam at warski dot org) @@ -53,7 +54,8 @@ public abstract class AbstractCompositeIdMapper extends AbstractIdMapper impleme Object ret; try { - ret = Thread.currentThread().getContextClassLoader().loadClass(compositeIdClass).newInstance(); + final Class clazz = ReflectHelper.classForName(compositeIdClass); + ret = ReflectHelper.getDefaultConstructor(clazz).newInstance(); } catch (Exception e) { throw new AuditException(e); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/MultipleIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/MultipleIdMapper.java index 3d8084e03a..1607971499 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/MultipleIdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/id/MultipleIdMapper.java @@ -29,6 +29,7 @@ import java.util.Map; import org.hibernate.envers.entities.PropertyData; import org.hibernate.envers.exception.AuditException; +import org.hibernate.internal.util.ReflectHelper; /** * @author Adam Warski (adam at warski dot org) @@ -75,7 +76,8 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl Object ret; try { - ret = Thread.currentThread().getContextClassLoader().loadClass(compositeIdClass).newInstance(); + final Class clazz = ReflectHelper.classForName(compositeIdClass); + ret = ReflectHelper.getDefaultConstructor(clazz).newInstance(); } catch (Exception e) { throw new AuditException(e); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java index 0eef07d19a..756f4b857d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.entities.mapper.relation; + import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -43,6 +44,7 @@ import org.hibernate.envers.entities.mapper.PropertyMapper; import org.hibernate.envers.entities.mapper.relation.lazy.initializor.Initializor; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.reader.AuditReaderImplementor; +import org.hibernate.envers.tools.Tools; import org.hibernate.envers.tools.reflection.ReflectionTools; import org.hibernate.property.Setter; @@ -53,13 +55,16 @@ import org.hibernate.property.Setter; public abstract class AbstractCollectionMapper implements PropertyMapper { protected final CommonCollectionMapperData commonCollectionMapperData; protected final Class collectionClass; + protected final boolean revisionTypeInId; private final Constructor proxyConstructor; protected AbstractCollectionMapper(CommonCollectionMapperData commonCollectionMapperData, - Class collectionClass, Class proxyClass) { + Class collectionClass, Class proxyClass, + boolean revisionTypeInId) { this.commonCollectionMapperData = commonCollectionMapperData; this.collectionClass = collectionClass; + this.revisionTypeInId = revisionTypeInId; try { proxyConstructor = proxyClass.getConstructor(Initializor.class); @@ -73,13 +78,14 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { /** * Maps the changed collection element to the given map. + * @param idData Map to which composite-id data should be added. * @param data Where to map the data. * @param changed The changed collection element to map. */ - protected abstract void mapToMapFromObject(Map data, Object changed); + protected abstract void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object changed); - private void addCollectionChanges(List collectionChanges, Set changed, - RevisionType revisionType, Serializable id) { + private void addCollectionChanges(SessionImplementor session, List collectionChanges, + Set changed, RevisionType revisionType, Serializable id) { for (Object changedObj : changed) { Map entityData = new HashMap(); Map originalId = new HashMap(); @@ -91,18 +97,18 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { commonCollectionMapperData.getReferencingIdData().getPrefixedMapper().mapToMapFromId(originalId, id); // Mapping collection element and index (if present). - mapToMapFromObject(originalId, changedObj); + mapToMapFromObject(session, originalId, entityData, changedObj); - entityData.put(commonCollectionMapperData.getVerEntCfg().getRevisionTypePropName(), revisionType); + (revisionTypeInId ? originalId : entityData).put(commonCollectionMapperData.getVerEntCfg().getRevisionTypePropName(), revisionType); } } @SuppressWarnings({"unchecked"}) - public List mapCollectionChanges(String referencingPropertyName, + public List mapCollectionChanges(SessionImplementor session, + String referencingPropertyName, PersistentCollection newColl, Serializable oldColl, Serializable id) { - if (!commonCollectionMapperData.getCollectionReferencingPropertyData().getName() - .equals(referencingPropertyName)) { + if (!commonCollectionMapperData.getCollectionReferencingPropertyData().getName().equals(referencingPropertyName)) { return null; } @@ -118,14 +124,14 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { // removeAll in AbstractSet has an implementation that is hashcode-change sensitive (as opposed to addAll). if (oldColl != null) { added.removeAll(new HashSet(oldCollection)); } - addCollectionChanges(collectionChanges, added, RevisionType.ADD, id); + addCollectionChanges(session, collectionChanges, added, RevisionType.ADD, id); Set deleted = new HashSet(); if (oldColl != null) { deleted.addAll(oldCollection); } // The same as above - re-hashing new collection. if (newColl != null) { deleted.removeAll(new HashSet(newCollection)); } - addCollectionChanges(collectionChanges, deleted, RevisionType.DEL, id); + addCollectionChanges(session, collectionChanges, deleted, RevisionType.DEL, id); return collectionChanges; } @@ -139,10 +145,13 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { public void mapModifiedFlagsToMapFromEntity(SessionImplementor session, Map data, Object newObj, Object oldObj) { PropertyData propertyData = commonCollectionMapperData.getCollectionReferencingPropertyData(); if (propertyData.isUsingModifiedFlag()) { - if(isFromNullToEmptyOrFromEmptyToNull((PersistentCollection) newObj, (Serializable) oldObj)){ + if (isNotPersistentCollection(newObj) || isNotPersistentCollection(oldObj)) { + // Compare POJOs. + data.put(propertyData.getModifiedFlagPropertyName(), !Tools.objectsEqual(newObj, oldObj)); + } else if (isFromNullToEmptyOrFromEmptyToNull((PersistentCollection) newObj, (Serializable) oldObj)) { data.put(propertyData.getModifiedFlagPropertyName(), true); } else { - List changes = mapCollectionChanges( + List changes = mapCollectionChanges(session, commonCollectionMapperData.getCollectionReferencingPropertyData().getName(), (PersistentCollection) newObj, (Serializable) oldObj, null); data.put(propertyData.getModifiedFlagPropertyName(), !changes.isEmpty()); @@ -150,6 +159,10 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { } } + private boolean isNotPersistentCollection(Object obj) { + return obj != null && !(obj instanceof PersistentCollection); + } + private boolean isFromNullToEmptyOrFromEmptyToNull(PersistentCollection newColl, Serializable oldColl) { // Comparing new and old collection content. Collection newCollection = getNewCollectionContent(newColl); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractToOneMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractToOneMapper.java index f038ecd1c9..b47a2cd3a7 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractToOneMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractToOneMapper.java @@ -11,8 +11,10 @@ import org.hibernate.envers.entities.EntityConfiguration; import org.hibernate.envers.entities.PropertyData; import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData; import org.hibernate.envers.entities.mapper.PropertyMapper; +import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.reflection.ReflectionTools; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.Setter; /** @@ -40,9 +42,9 @@ public abstract class AbstractToOneMapper implements PropertyMapper { } @Override - public List mapCollectionChanges(String referencingPropertyName, - PersistentCollection newColl, - Serializable oldColl, Serializable id) { + public List mapCollectionChanges(SessionImplementor session, String referencingPropertyName, + PersistentCollection newColl, Serializable oldColl, + Serializable id) { return null; } @@ -59,7 +61,13 @@ public abstract class AbstractToOneMapper implements PropertyMapper { entCfg = verCfg.getEntCfg().getNotVersionEntityConfiguration(entityName); isRelationAudited = false; } - Class entityClass = ReflectionTools.loadClass(entCfg.getEntityClassName()); + Class entityClass; + try { + entityClass = ReflectHelper.classForName(entCfg.getEntityClassName()); + } + catch ( ClassNotFoundException e ) { + throw new AuditException( e ); + } return new EntityInfo(entityClass, entityName, isRelationAudited); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java index a17c908dab..1c934f6f7a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.Map; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.mapper.PropertyMapper; import org.hibernate.envers.entities.mapper.relation.lazy.initializor.BasicCollectionInitializor; @@ -42,8 +43,8 @@ public class BasicCollectionMapper extends AbstractCollect public BasicCollectionMapper(CommonCollectionMapperData commonCollectionMapperData, Class collectionClass, Class proxyClass, - MiddleComponentData elementComponentData) { - super(commonCollectionMapperData, collectionClass, proxyClass); + MiddleComponentData elementComponentData, boolean revisionTypeInId) { + super(commonCollectionMapperData, collectionClass, proxyClass, revisionTypeInId); this.elementComponentData = elementComponentData; } @@ -67,7 +68,7 @@ public class BasicCollectionMapper extends AbstractCollect } } - protected void mapToMapFromObject(Map data, Object changed) { - elementComponentData.getComponentMapper().mapToMapFromObject(data, changed); + protected void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object changed) { + elementComponentData.getComponentMapper().mapToMapFromObject(session, idData, data, changed); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java index 1bb3022cdc..f883e68a79 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.mapper.PropertyMapper; import org.hibernate.envers.entities.mapper.relation.lazy.initializor.Initializor; @@ -45,8 +46,9 @@ public final class ListCollectionMapper extends AbstractCollectionMapper i private final MiddleComponentData indexComponentData; public ListCollectionMapper(CommonCollectionMapperData commonCollectionMapperData, - MiddleComponentData elementComponentData, MiddleComponentData indexComponentData) { - super(commonCollectionMapperData, List.class, ListProxy.class); + MiddleComponentData elementComponentData, MiddleComponentData indexComponentData, + boolean revisionTypeInId) { + super(commonCollectionMapperData, List.class, ListProxy.class, revisionTypeInId); this.elementComponentData = elementComponentData; this.indexComponentData = indexComponentData; } @@ -76,9 +78,9 @@ public final class ListCollectionMapper extends AbstractCollectionMapper i } @SuppressWarnings({"unchecked"}) - protected void mapToMapFromObject(Map data, Object changed) { + protected void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object changed) { Pair indexValuePair = (Pair) changed; - elementComponentData.getComponentMapper().mapToMapFromObject(data, indexValuePair.getSecond()); - indexComponentData.getComponentMapper().mapToMapFromObject(data, indexValuePair.getFirst()); + elementComponentData.getComponentMapper().mapToMapFromObject(session, idData, data, indexValuePair.getSecond()); + indexComponentData.getComponentMapper().mapToMapFromObject(session, idData, data, indexValuePair.getFirst()); } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java index 519b7db038..44f1ec8827 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.Map; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.mapper.PropertyMapper; import org.hibernate.envers.entities.mapper.relation.lazy.initializor.Initializor; @@ -43,8 +44,9 @@ public class MapCollectionMapper extends AbstractCollectionMapper public MapCollectionMapper(CommonCollectionMapperData commonCollectionMapperData, Class collectionClass, Class proxyClass, - MiddleComponentData elementComponentData, MiddleComponentData indexComponentData) { - super(commonCollectionMapperData, collectionClass, proxyClass); + MiddleComponentData elementComponentData, MiddleComponentData indexComponentData, + boolean revisionTypeInId) { + super(commonCollectionMapperData, collectionClass, proxyClass, revisionTypeInId); this.elementComponentData = elementComponentData; this.indexComponentData = indexComponentData; } @@ -71,8 +73,8 @@ public class MapCollectionMapper extends AbstractCollectionMapper } } - protected void mapToMapFromObject(Map data, Object changed) { - elementComponentData.getComponentMapper().mapToMapFromObject(data, ((Map.Entry) changed).getValue()); - indexComponentData.getComponentMapper().mapToMapFromObject(data, ((Map.Entry) changed).getKey()); + protected void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object changed) { + elementComponentData.getComponentMapper().mapToMapFromObject(session, idData, data, ((Map.Entry) changed).getValue()); + indexComponentData.getComponentMapper().mapToMapFromObject(session, idData, data, ((Map.Entry) changed).getKey()); } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java index ab04a8d96d..a3a512ebd1 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java @@ -39,8 +39,9 @@ public final class SortedMapCollectionMapper extends MapCollectionMapper collectionClass, Class proxyClass, - MiddleComponentData elementComponentData, MiddleComponentData indexComponentData, Comparator comparator) { - super(commonCollectionMapperData, collectionClass, proxyClass, elementComponentData, indexComponentData); + MiddleComponentData elementComponentData, MiddleComponentData indexComponentData, Comparator comparator, + boolean revisionTypeInId) { + super(commonCollectionMapperData, collectionClass, proxyClass, elementComponentData, indexComponentData, revisionTypeInId); this.comparator = comparator; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java index b5555fe8c7..7c4a3b451a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java @@ -39,8 +39,9 @@ public final class SortedSetCollectionMapper extends BasicCollectionMapper collectionClass, Class proxyClass, - MiddleComponentData elementComponentData, Comparator comparator) { - super(commonCollectionMapperData, collectionClass, proxyClass, elementComponentData); + MiddleComponentData elementComponentData, Comparator comparator, + boolean revisionTypeInId) { + super(commonCollectionMapperData, collectionClass, proxyClass, elementComponentData, revisionTypeInId); this.comparator = comparator; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java index 308ffe42ba..d322340638 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java @@ -34,6 +34,7 @@ import org.hibernate.envers.entities.mapper.id.IdMapper; import org.hibernate.envers.entities.mapper.relation.lazy.ToOneDelegateSessionImplementor; import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.Tools; +import org.hibernate.envers.tools.query.Parameters; import org.hibernate.persister.entity.EntityPersister; /** @@ -105,4 +106,8 @@ public class ToOneIdMapper extends AbstractToOneMapper { setPropertyValue(obj, value); } + + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { + delegate.addIdsEqualToQuery( parameters, prefix1, delegate, prefix2 ); + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleComponentMapper.java index 3630509b01..73775eb4c8 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleComponentMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleComponentMapper.java @@ -22,8 +22,12 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.entities.mapper.relation.component; -import java.util.Map; +import java.util.Map; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; + +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.tools.query.Parameters; @@ -45,18 +49,22 @@ public interface MiddleComponentMapper { /** * Maps from an object to the object's map representation (for an entity - only its id). + * @param session The current session. + * @param idData Map to which composite-id data should be added. * @param data Map to which data should be added. * @param obj Object to map from. */ - void mapToMapFromObject(Map data, Object obj); + void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj); /** * Adds query statements, which contains restrictions, which express the property that part of the middle * entity with alias prefix1, is equal to part of the middle entity with alias prefix2 (the entity is the same). * The part is the component's representation in the middle entity. * @param parameters Parameters, to which to add the statements. + * @param idPrefix1 First alias of the entity + prefix + id to add to the properties. * @param prefix1 First alias of the entity + prefix to add to the properties. + * @param idPrefix2 Second alias of the entity + prefix + id to add to the properties. * @param prefix2 Second alias of the entity + prefix to add to the properties. */ - void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2); + void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleDummyComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleDummyComponentMapper.java index 1c733a7b5c..8c681b937f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleDummyComponentMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleDummyComponentMapper.java @@ -24,6 +24,7 @@ package org.hibernate.envers.entities.mapper.relation.component; import java.util.Map; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.tools.query.Parameters; @@ -36,9 +37,9 @@ public final class MiddleDummyComponentMapper implements MiddleComponentMapper { return null; } - public void mapToMapFromObject(Map data, Object obj) { + public void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj) { } - public void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2) { + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleEmbeddableComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleEmbeddableComponentMapper.java new file mode 100644 index 0000000000..c287ff6c8d --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleEmbeddableComponentMapper.java @@ -0,0 +1,115 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.entities.mapper.relation.component; + +import java.util.Map; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.envers.entities.EntityInstantiator; +import org.hibernate.envers.entities.PropertyData; +import org.hibernate.envers.entities.mapper.CompositeMapperBuilder; +import org.hibernate.envers.entities.mapper.MultiPropertyMapper; +import org.hibernate.envers.entities.mapper.PropertyMapper; +import org.hibernate.envers.entities.mapper.relation.ToOneIdMapper; +import org.hibernate.envers.exception.AuditException; +import org.hibernate.envers.tools.query.Parameters; +import org.hibernate.internal.util.ReflectHelper; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +public class MiddleEmbeddableComponentMapper implements MiddleComponentMapper, CompositeMapperBuilder { + private final MultiPropertyMapper delegate; + private final Class componentClass; + + public MiddleEmbeddableComponentMapper(MultiPropertyMapper delegate, String componentClassName) { + this.delegate = delegate; + try { + componentClass = Thread.currentThread().getContextClassLoader().loadClass( componentClassName ); + } + catch ( Exception e ) { + throw new AuditException( e ); + } + } + + @Override + public Object mapToObjectFromFullMap(EntityInstantiator entityInstantiator, Map data, Object dataObject, Number revision) { + try { + final Object componentInstance = dataObject != null ? dataObject : ReflectHelper.getDefaultConstructor( componentClass ).newInstance(); + delegate.mapToEntityFromMap( + entityInstantiator.getAuditConfiguration(), componentInstance, data, null, + entityInstantiator.getAuditReaderImplementor(), revision + ); + return componentInstance; + } + catch ( Exception e ) { + throw new AuditException( e ); + } + } + + @Override + public void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj) { + delegate.mapToMapFromEntity( session, data, obj, obj ); + } + + @Override + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { + addMiddleEqualToQuery( delegate, parameters, idPrefix1, prefix1, idPrefix2, prefix2 ); + } + + protected void addMiddleEqualToQuery(CompositeMapperBuilder compositeMapper, Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { + for ( final Map.Entry entry : compositeMapper.getProperties().entrySet() ) { + final String propertyName = entry.getKey().getName(); + final PropertyMapper nestedMapper = entry.getValue(); + if ( nestedMapper instanceof CompositeMapperBuilder ) { + addMiddleEqualToQuery( (CompositeMapperBuilder) nestedMapper, parameters, idPrefix1, prefix1, idPrefix2, prefix2 ); + } + else if ( nestedMapper instanceof ToOneIdMapper ) { + ( (ToOneIdMapper) nestedMapper ).addMiddleEqualToQuery( parameters, idPrefix1, prefix1, idPrefix2, prefix2 ); + } + else { + parameters.addWhere( prefix1 + '.' + propertyName, false, "=", prefix2 + '.' + propertyName, false ); + } + } + } + + @Override + public CompositeMapperBuilder addComponent(PropertyData propertyData, String componentClassName) { + return delegate.addComponent( propertyData, componentClassName ); + } + + @Override + public void addComposite(PropertyData propertyData, PropertyMapper propertyMapper) { + delegate.addComposite( propertyData, propertyMapper ); + } + + @Override + public void add(PropertyData propertyData) { + delegate.add( propertyData ); + } + + public Map getProperties() { + return delegate.getProperties(); + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyIdComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyIdComponentMapper.java index b0b48b8155..464f9a2c24 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyIdComponentMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyIdComponentMapper.java @@ -24,6 +24,7 @@ package org.hibernate.envers.entities.mapper.relation.component; import java.util.Map; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.entities.mapper.id.IdMapper; @@ -49,11 +50,11 @@ public final class MiddleMapKeyIdComponentMapper implements MiddleComponentMappe return relatedIdMapper.mapToIdFromMap((Map) data.get(verEntCfg.getOriginalIdPropName())); } - public void mapToMapFromObject(Map data, Object obj) { + public void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj) { // Doing nothing. } - public void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2) { + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { // Doing nothing. } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyPropertyComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyPropertyComponentMapper.java index 7739976397..f619e01ae7 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyPropertyComponentMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleMapKeyPropertyComponentMapper.java @@ -24,6 +24,7 @@ package org.hibernate.envers.entities.mapper.relation.component; import java.util.Map; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.reflection.ReflectionTools; @@ -49,11 +50,11 @@ public class MiddleMapKeyPropertyComponentMapper implements MiddleComponentMappe return ReflectionTools.getGetter(dataObject.getClass(), propertyName, accessType).get(dataObject); } - public void mapToMapFromObject(Map data, Object obj) { + public void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj) { // Doing nothing. } - public void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2) { + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { // Doing nothing. } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleRelatedComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleRelatedComponentMapper.java index 40de4daa18..fb43169c9b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleRelatedComponentMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleRelatedComponentMapper.java @@ -24,6 +24,7 @@ package org.hibernate.envers.entities.mapper.relation.component; import java.util.Map; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; import org.hibernate.envers.tools.query.Parameters; @@ -43,11 +44,11 @@ public final class MiddleRelatedComponentMapper implements MiddleComponentMapper return entityInstantiator.createInstanceFromVersionsEntity(relatedIdData.getEntityName(), data, revision); } - public void mapToMapFromObject(Map data, Object obj) { - relatedIdData.getPrefixedMapper().mapToMapFromEntity(data, obj); + public void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj) { + relatedIdData.getPrefixedMapper().mapToMapFromEntity(idData, obj); } - public void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2) { - relatedIdData.getPrefixedMapper().addIdsEqualToQuery(parameters, prefix1, prefix2); + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { + relatedIdData.getPrefixedMapper().addIdsEqualToQuery(parameters, idPrefix1, idPrefix2); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleSimpleComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleSimpleComponentMapper.java index 3cf72b97c6..f6cc572943 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleSimpleComponentMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleSimpleComponentMapper.java @@ -24,6 +24,7 @@ package org.hibernate.envers.entities.mapper.relation.component; import java.util.Map; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.tools.query.Parameters; @@ -46,11 +47,11 @@ public final class MiddleSimpleComponentMapper implements MiddleComponentMapper return ((Map) data.get(verEntCfg.getOriginalIdPropName())).get(propertyName); } - public void mapToMapFromObject(Map data, Object obj) { - data.put(propertyName, obj); + public void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj) { + idData.put(propertyName, obj); } - public void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2) { - parameters.addWhere(prefix1 + "." + propertyName, false, "=", prefix2 + "." + propertyName, false); + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { + parameters.addWhere(idPrefix1 + "." + propertyName, false, "=", idPrefix2 + "." + propertyName, false); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java index 049b6ca960..30fdd16535 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java @@ -24,6 +24,7 @@ package org.hibernate.envers.entities.mapper.relation.component; import java.util.Map; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.tools.query.Parameters; @@ -45,11 +46,11 @@ public final class MiddleStraightComponentMapper implements MiddleComponentMappe return data.get(propertyName); } - public void mapToMapFromObject(Map data, Object obj) { - data.put(propertyName, obj); - } + public void mapToMapFromObject(SessionImplementor session, Map idData, Map data, Object obj) { + idData.put(propertyName, obj); + } - public void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2) { + public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1, String idPrefix2, String prefix2) { throw new UnsupportedOperationException("Cannot use this mapper with a middle table!"); } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/AbstractDelegateSessionImplementor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/AbstractDelegateSessionImplementor.java index 1082c05335..80e5317c04 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/AbstractDelegateSessionImplementor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/AbstractDelegateSessionImplementor.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import org.hibernate.CacheMode; +import org.hibernate.Criteria; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Interceptor; @@ -48,7 +49,6 @@ import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.transaction.spi.TransactionCoordinator; -import org.hibernate.internal.CriteriaImpl; import org.hibernate.loader.custom.CustomQuery; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.Type; @@ -160,12 +160,12 @@ public abstract class AbstractDelegateSessionImplementor implements SessionImple } @Override - public ScrollableResults scroll(CriteriaImpl criteria, ScrollMode scrollMode) { + public ScrollableResults scroll(Criteria criteria, ScrollMode scrollMode) { return delegate.scroll(criteria, scrollMode); } @Override - public List list(CriteriaImpl criteria) { + public List list(Criteria criteria) { return delegate.list(criteria); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java index 04cb53fd4c..9a3bcda897 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java @@ -23,6 +23,7 @@ */ package org.hibernate.envers.entities.mapper.relation.lazy.initializor; +import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -32,6 +33,7 @@ import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.query.RelationQueryGenerator; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.reader.AuditReaderImplementor; +import org.hibernate.internal.util.ReflectHelper; /** * Initializes a non-indexed java collection (set or list, eventually sorted). @@ -55,11 +57,13 @@ public class BasicCollectionInitializor extends AbstractCo protected T initializeCollection(int size) { try { - return collectionClass.newInstance(); + return (T) ReflectHelper.getDefaultConstructor(collectionClass).newInstance(); } catch (InstantiationException e) { throw new AuditException(e); } catch (IllegalAccessException e) { throw new AuditException(e); + } catch (InvocationTargetException e) { + throw new AuditException(e); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/MapCollectionInitializor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/MapCollectionInitializor.java index df8ec77252..5b75027a6b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/MapCollectionInitializor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/MapCollectionInitializor.java @@ -23,6 +23,7 @@ */ package org.hibernate.envers.entities.mapper.relation.lazy.initializor; +import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; @@ -31,6 +32,7 @@ import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.query.RelationQueryGenerator; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.reader.AuditReaderImplementor; +import org.hibernate.internal.util.ReflectHelper; /** * Initializes a map. @@ -57,11 +59,13 @@ public class MapCollectionInitializor extends AbstractCollectionI protected T initializeCollection(int size) { try { - return collectionClass.newInstance(); + return (T) ReflectHelper.getDefaultConstructor(collectionClass).newInstance(); } catch (InstantiationException e) { throw new AuditException(e); } catch (IllegalAccessException e) { throw new AuditException(e); + } catch (InvocationTargetException e) { + throw new AuditException(e); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/AbstractRelationQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/AbstractRelationQueryGenerator.java new file mode 100644 index 0000000000..cb3fe4a696 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/AbstractRelationQueryGenerator.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.entities.mapper.relation.query; + +import org.hibernate.Query; +import org.hibernate.envers.RevisionType; +import org.hibernate.envers.configuration.AuditEntitiesConfiguration; +import org.hibernate.envers.entities.mapper.id.QueryParameterData; +import org.hibernate.envers.entities.mapper.relation.MiddleIdData; +import org.hibernate.envers.reader.AuditReaderImplementor; + +import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER; +import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER; + +/** + * Base class for implementers of {@code RelationQueryGenerator} contract. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public abstract class AbstractRelationQueryGenerator implements RelationQueryGenerator { + protected final AuditEntitiesConfiguration verEntCfg; + protected final MiddleIdData referencingIdData; + protected final boolean revisionTypeInId; + + protected AbstractRelationQueryGenerator(AuditEntitiesConfiguration verEntCfg, MiddleIdData referencingIdData, + boolean revisionTypeInId) { + this.verEntCfg = verEntCfg; + this.referencingIdData = referencingIdData; + this.revisionTypeInId = revisionTypeInId; + } + + protected abstract String getQueryString(); + + public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { + Query query = versionsReader.getSession().createQuery( getQueryString() ); + query.setParameter( REVISION_PARAMETER, revision ); + query.setParameter( DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL ); + for ( QueryParameterData paramData : referencingIdData.getPrefixedMapper().mapToQueryParametersFromId( primaryKey ) ) { + paramData.setParameterValue( query ); + } + return query; + } + + protected String getRevisionTypePath() { + return revisionTypeInId + ? verEntCfg.getOriginalIdPropName() + "." + verEntCfg.getRevisionTypePropName() + : verEntCfg.getRevisionTypePropName(); + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java index 9d92fcbe8a..953be5ee0d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java @@ -45,15 +45,15 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants * Selects data from an audit entity. * @author Adam Warski (adam at warski dot org) */ -public final class OneAuditEntityQueryGenerator implements RelationQueryGenerator { +public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGenerator { private final String queryString; - private final MiddleIdData referencingIdData; public OneAuditEntityQueryGenerator(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy, MiddleIdData referencingIdData, - String referencedEntityName, MiddleIdData referencedIdData) { - this.referencingIdData = referencingIdData; + String referencedEntityName, MiddleIdData referencedIdData, + boolean revisionTypeInId) { + super( verEntCfg, referencingIdData, revisionTypeInId ); /* * The query that we need to create: @@ -93,21 +93,15 @@ public final class OneAuditEntityQueryGenerator implements RelationQueryGenerato revisionPropertyPath, originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR); // e.revision_type != DEL - rootParameters.addWhereWithNamedParam(verEntCfg.getRevisionTypePropName(), false, "!=", DEL_REVISION_TYPE_PARAMETER); + rootParameters.addWhereWithNamedParam(getRevisionTypePath(), false, "!=", DEL_REVISION_TYPE_PARAMETER); StringBuilder sb = new StringBuilder(); qb.build(sb, Collections.emptyMap()); queryString = sb.toString(); } - public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { - Query query = versionsReader.getSession().createQuery(queryString); - query.setParameter(REVISION_PARAMETER, revision); - query.setParameter(DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL); - for (QueryParameterData paramData: referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(primaryKey)) { - paramData.setParameterValue(query); - } - - return query; - } + @Override + protected String getQueryString() { + return queryString; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java index cda7105467..45d8e47728 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java @@ -44,16 +44,16 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants * Selects data from a relation middle-table only. * @author Adam Warski (adam at warski dot org) */ -public final class OneEntityQueryGenerator implements RelationQueryGenerator { +public final class OneEntityQueryGenerator extends AbstractRelationQueryGenerator { private final String queryString; - private final MiddleIdData referencingIdData; public OneEntityQueryGenerator(AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy, String versionsMiddleEntityName, MiddleIdData referencingIdData, + boolean revisionTypeInId, MiddleComponentData... componentDatas) { - this.referencingIdData = referencingIdData; + super( verEntCfg, referencingIdData, revisionTypeInId ); /* * The query that we need to create: @@ -90,25 +90,19 @@ public final class OneEntityQueryGenerator implements RelationQueryGenerator { // (with ee association at revision :revision) // --> based on auditStrategy (see above) auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, - verEntCfg.getRevisionEndFieldName(), true,referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, componentDatas); + verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName, + eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); // ee.revision_type != DEL - rootParameters.addWhereWithNamedParam(verEntCfg.getRevisionTypePropName(), "!=", DEL_REVISION_TYPE_PARAMETER); + rootParameters.addWhereWithNamedParam(getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER); StringBuilder sb = new StringBuilder(); qb.build(sb, Collections.emptyMap()); queryString = sb.toString(); } - public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { - Query query = versionsReader.getSession().createQuery(queryString); - query.setParameter(REVISION_PARAMETER, revision); - query.setParameter(DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL); - for (QueryParameterData paramData: referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(primaryKey)) { - paramData.setParameterValue(query); - } - - return query; - } + @Override + protected String getQueryString() { + return queryString; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java index 0a13260127..782d12cea3 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java @@ -49,9 +49,8 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants * Selects data from a relation middle-table and a two related versions entity. * @author Adam Warski (adam at warski dot org) */ -public final class ThreeEntityQueryGenerator implements RelationQueryGenerator { +public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenerator { private final String queryString; - private final MiddleIdData referencingIdData; public ThreeEntityQueryGenerator(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, @@ -60,8 +59,9 @@ public final class ThreeEntityQueryGenerator implements RelationQueryGenerator { MiddleIdData referencingIdData, MiddleIdData referencedIdData, MiddleIdData indexIdData, + boolean revisionTypeInId, MiddleComponentData... componentDatas) { - this.referencingIdData = referencingIdData; + super( verEntCfg, referencingIdData, revisionTypeInId ); /* * The query that we need to create: @@ -157,28 +157,23 @@ public final class ThreeEntityQueryGenerator implements RelationQueryGenerator { // --> based on auditStrategy (see above) auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, componentDatas); + eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); // ee.revision_type != DEL - rootParameters.addWhereWithNamedParam(verEntCfg.getRevisionTypePropName(), "!=", DEL_REVISION_TYPE_PARAMETER); + String revisionTypePropName = getRevisionTypePath(); + rootParameters.addWhereWithNamedParam(revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER); // e.revision_type != DEL - rootParameters.addWhereWithNamedParam(REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionTypePropName(), false, "!=", DEL_REVISION_TYPE_PARAMETER); + rootParameters.addWhereWithNamedParam(REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER); // f.revision_type != DEL - rootParameters.addWhereWithNamedParam(INDEX_ENTITY_ALIAS + "." + verEntCfg.getRevisionTypePropName(), false, "!=", DEL_REVISION_TYPE_PARAMETER); + rootParameters.addWhereWithNamedParam(INDEX_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER); StringBuilder sb = new StringBuilder(); qb.build(sb, Collections.emptyMap()); queryString = sb.toString(); } - public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { - Query query = versionsReader.getSession().createQuery(queryString); - query.setParameter(REVISION_PARAMETER, revision); - query.setParameter(DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL); - for (QueryParameterData paramData: referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(primaryKey)) { - paramData.setParameterValue(query); - } - - return query; - } + @Override + protected String getQueryString() { + return queryString; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java index 13ddb92247..d5402cd5cd 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java @@ -45,16 +45,16 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants * Selects data from a relation middle-table and a related non-audited entity. * @author Adam Warski (adam at warski dot org) */ -public final class TwoEntityOneAuditedQueryGenerator implements RelationQueryGenerator { +public final class TwoEntityOneAuditedQueryGenerator extends AbstractRelationQueryGenerator { private final String queryString; - private final MiddleIdData referencingIdData; public TwoEntityOneAuditedQueryGenerator(AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy, String versionsMiddleEntityName, MiddleIdData referencingIdData, MiddleIdData referencedIdData, + boolean revisionTypeInId, MiddleComponentData... componentDatas) { - this.referencingIdData = referencingIdData; + super( verEntCfg, referencingIdData, revisionTypeInId ); /* * The query that we need to create: @@ -99,24 +99,18 @@ public final class TwoEntityOneAuditedQueryGenerator implements RelationQueryGen // --> based on auditStrategy (see above) auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true,referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, componentDatas); + eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); // ee.revision_type != DEL - rootParameters.addWhereWithNamedParam(verEntCfg.getRevisionTypePropName(), "!=", DEL_REVISION_TYPE_PARAMETER); + rootParameters.addWhereWithNamedParam(getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER); StringBuilder sb = new StringBuilder(); qb.build(sb, Collections.emptyMap()); queryString = sb.toString(); } - public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { - Query query = versionsReader.getSession().createQuery(queryString); - query.setParameter(REVISION_PARAMETER, revision); - query.setParameter(DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL); - for (QueryParameterData paramData: referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(primaryKey)) { - paramData.setParameterValue(query); - } - - return query; - } + @Override + protected String getQueryString() { + return queryString; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java index aa5f5292f9..42642bd7c6 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java @@ -47,9 +47,8 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants * Selects data from a relation middle-table and a related versions entity. * @author Adam Warski (adam at warski dot org) */ -public final class TwoEntityQueryGenerator implements RelationQueryGenerator { +public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerator { private final String queryString; - private final MiddleIdData referencingIdData; public TwoEntityQueryGenerator(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, @@ -57,8 +56,9 @@ public final class TwoEntityQueryGenerator implements RelationQueryGenerator { String versionsMiddleEntityName, MiddleIdData referencingIdData, MiddleIdData referencedIdData, + boolean revisionTypeInId, MiddleComponentData... componentDatas) { - this.referencingIdData = referencingIdData; + super( verEntCfg, referencingIdData, revisionTypeInId ); /* * The query that we need to create: @@ -118,26 +118,21 @@ public final class TwoEntityQueryGenerator implements RelationQueryGenerator { // --> based on auditStrategy (see above) auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, componentDatas); + eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); // ee.revision_type != DEL - rootParameters.addWhereWithNamedParam(verEntCfg.getRevisionTypePropName(), "!=", DEL_REVISION_TYPE_PARAMETER); + String revisionTypePropName = getRevisionTypePath(); + rootParameters.addWhereWithNamedParam(revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER); // e.revision_type != DEL - rootParameters.addWhereWithNamedParam(REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionTypePropName(), false, "!=", DEL_REVISION_TYPE_PARAMETER); + rootParameters.addWhereWithNamedParam(REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER); StringBuilder sb = new StringBuilder(); qb.build(sb, Collections.emptyMap()); queryString = sb.toString(); } - public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { - Query query = versionsReader.getSession().createQuery(queryString); - query.setParameter(REVISION_PARAMETER, revision); - query.setParameter(DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL); - for (QueryParameterData paramData: referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(primaryKey)) { - paramData.setParameterValue(query); - } - - return query; - } + @Override + protected String getQueryString() { + return queryString; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java b/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java index c79a3c6134..b6ee70c6de 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java @@ -156,7 +156,7 @@ public abstract class BaseEnversCollectionEventListener extends BaseEnversEventL .getEntCfg() .get( collectionEntityName ) .getPropertyMapper() - .mapCollectionChanges( referencingPropertyName, newColl, oldColl, event.getAffectedOwnerIdOrNull() ); + .mapCollectionChanges( event.getSession(), referencingPropertyName, newColl, oldColl, event.getAffectedOwnerIdOrNull() ); // Getting the id mapper for the related entity, as the work units generated will corrspond to the related // entities. diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/AuditEntity.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/AuditEntity.java index df770f2250..a59f1b9eb7 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/AuditEntity.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/AuditEntity.java @@ -22,6 +22,8 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query; + +import org.hibernate.criterion.Restrictions; import org.hibernate.envers.RevisionType; import org.hibernate.envers.query.criteria.AuditConjunction; import org.hibernate.envers.query.criteria.AuditCriterion; @@ -39,7 +41,7 @@ import org.hibernate.envers.query.property.RevisionTypePropertyName; /** * TODO: ilike * @author Adam Warski (adam at warski dot org) - * @see org.hibernate.criterion.Restrictions + * @see Restrictions */ @SuppressWarnings({"JavaDoc"}) public class AuditEntity { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java index bf6518417c..23077445a4 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java @@ -27,6 +27,7 @@ import java.util.List; import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -56,8 +57,9 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit return this; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); CriteriaTools.checkPropertyNotARelation(auditCfg, entityName, propertyName); @@ -69,8 +71,8 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit // Adding all specified conditions both to the main query, as well as to the // aggregated one. for (AuditCriterion versionsCriteria : criterions) { - versionsCriteria.addToQuery(auditCfg, entityName, qb, subParams); - versionsCriteria.addToQuery(auditCfg, entityName, subQb, subQb.getRootParameters()); + versionsCriteria.addToQuery(auditCfg, versionsReader, entityName, qb, subParams); + versionsCriteria.addToQuery(auditCfg, versionsReader, entityName, subQb, subQb.getRootParameters()); } // Setting the desired projection of the aggregated query diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java index fa9a75c840..8c5b28d460 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -44,14 +45,15 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion { return this; } - public void addToQuery(AuditConfiguration verCfg, String entityName, QueryBuilder qb, Parameters parameters) { + public void addToQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { Parameters andParameters = parameters.addSubParameters(Parameters.AND); if (criterions.size() == 0) { andParameters.addWhere("1", false, "=", "1", false); } else { for (AuditCriterion criterion : criterions) { - criterion.addToQuery(verCfg, entityName, qb, andParameters); + criterion.addToQuery(verCfg, versionsReader, entityName, qb, andParameters); } } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java index 281856e33d..cdc53caa8b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java @@ -22,7 +22,9 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -30,5 +32,6 @@ import org.hibernate.envers.tools.query.QueryBuilder; * @author Adam Warski (adam at warski dot org) */ public interface AuditCriterion { - void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters); + void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java index 2221175068..401bcfa0d5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -44,14 +45,15 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion { return this; } - public void addToQuery(AuditConfiguration verCfg, String entityName, QueryBuilder qb, Parameters parameters) { + public void addToQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { Parameters orParameters = parameters.addSubParameters(Parameters.OR); if (criterions.size() == 0) { orParameters.addWhere("0", false, "=", "1", false); } else { for (AuditCriterion criterion : criterions) { - criterion.addToQuery(verCfg, entityName, qb, orParameters); + criterion.addToQuery(verCfg, versionsReader, entityName, qb, orParameters); } } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditId.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditId.java index e1e23b05c1..08f5bd5823 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditId.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditId.java @@ -21,40 +21,63 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ - package org.hibernate.envers.query.criteria; + import org.hibernate.envers.query.projection.AuditProjection; import org.hibernate.envers.query.projection.PropertyAuditProjection; +import org.hibernate.envers.query.property.EntityPropertyName; import org.hibernate.envers.query.property.OriginalIdPropertyName; +import org.hibernate.envers.query.property.PropertyNameGetter; /** * Create restrictions and projections for the id of an audited entity. + * * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ -@SuppressWarnings({"JavaDoc"}) -public class AuditId { - /** +@SuppressWarnings({ "JavaDoc" }) +public class AuditId extends AuditProperty { + public static final String IDENTIFIER_PLACEHOLDER = "$$id$$"; + private static final PropertyNameGetter identifierPropertyGetter = new EntityPropertyName( IDENTIFIER_PLACEHOLDER ); + + public AuditId() { + super( identifierPropertyGetter ); + } + + /** * Apply an "equal" constraint */ public AuditCriterion eq(Object id) { - return new IdentifierEqAuditExpression(id, true); + return new IdentifierEqAuditExpression( id, true ); } - /** + /** * Apply a "not equal" constraint */ public AuditCriterion ne(Object id) { - return new IdentifierEqAuditExpression(id, false); + return new IdentifierEqAuditExpression( id, false ); } - // Projections + // Projections - /** - * Projection counting the values - * TODO: idPropertyName isn't needed, should be read from the configuration - * @param idPropertyName Name of the identifier property - */ - public AuditProjection count(String idPropertyName) { - return new PropertyAuditProjection(new OriginalIdPropertyName(idPropertyName), "count", false); - } + /** + * Projection counting the values + * + * @param idPropertyName Name of the identifier property + * + * @deprecated Use {@link #count()}. + */ + public AuditProjection count(String idPropertyName) { + return new PropertyAuditProjection( new OriginalIdPropertyName( idPropertyName ), "count", false ); + } + + @Override + public AuditCriterion hasChanged() { + throw new UnsupportedOperationException(); + } + + @Override + public AuditCriterion hasNotChanged() { + throw new UnsupportedOperationException(); + } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/BetweenAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/BetweenAuditExpression.java index c2b289f71d..14f44a81e1 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/BetweenAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/BetweenAuditExpression.java @@ -24,6 +24,7 @@ package org.hibernate.envers.query.criteria; import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -41,8 +42,9 @@ public class BetweenAuditExpression implements AuditCriterion { this.hi = hi; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); CriteriaTools.checkPropertyNotARelation(auditCfg, entityName, propertyName); Parameters subParams = parameters.addSubParameters(Parameters.AND); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/CriteriaTools.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/CriteriaTools.java index 62cab0e567..46bb2ebfde 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/CriteriaTools.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/CriteriaTools.java @@ -22,10 +22,13 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.RelationDescription; import org.hibernate.envers.entities.RelationType; import org.hibernate.envers.exception.AuditException; +import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; /** * @author Adam Warski (adam at warski dot org) @@ -56,4 +59,28 @@ public class CriteriaTools { throw new AuditException("This type of relation (" + entityName + "." + propertyName + ") isn't supported and can't be used in queries."); } + + /** + * @see #determinePropertyName(AuditConfiguration, AuditReaderImplementor, String, String) + */ + public static String determinePropertyName(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, + String entityName, PropertyNameGetter propertyNameGetter) { + return determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter.get( auditCfg ) ); + } + + /** + * @param auditCfg Audit configuration. + * @param versionsReader Versions reader. + * @param entityName Original entity name (not audited). + * @param propertyName Property name or placeholder. + * @return Path to property. Handles identifier placeholder used by {@link AuditId}. + */ + public static String determinePropertyName(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, + String entityName, String propertyName) { + if ( AuditId.IDENTIFIER_PLACEHOLDER.equals( propertyName ) ) { + final String identifierPropertyName = versionsReader.getSessionImplementor().getFactory().getEntityPersister( entityName ).getIdentifierPropertyName(); + propertyName = auditCfg.getAuditEntCfg().getOriginalIdPropName() + "." + identifierPropertyName; + } + return propertyName; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/IdentifierEqAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/IdentifierEqAuditExpression.java index df5558ac7b..a6346aad01 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/IdentifierEqAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/IdentifierEqAuditExpression.java @@ -23,6 +23,7 @@ */ package org.hibernate.envers.query.criteria; import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -39,7 +40,8 @@ public class IdentifierEqAuditExpression implements AuditCriterion { this.equals = equals; } - public void addToQuery(AuditConfiguration verCfg, String entityName, QueryBuilder qb, Parameters parameters) { + public void addToQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { verCfg.getEntCfg().get(entityName).getIdMapper() .addIdEqualsToQuery(parameters, id, verCfg.getAuditEntCfg().getOriginalIdPropName(), equals); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/InAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/InAuditExpression.java index fb13d91560..1c2e5b3165 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/InAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/InAuditExpression.java @@ -22,8 +22,10 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -39,8 +41,9 @@ public class InAuditExpression implements AuditCriterion { this.values = values; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); CriteriaTools.checkPropertyNotARelation(auditCfg, entityName, propertyName); parameters.addWhereWithParams(propertyName, "in (", values, ")"); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/LogicalAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/LogicalAuditExpression.java index d4fd3de965..5ab9f0612e 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/LogicalAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/LogicalAuditExpression.java @@ -23,6 +23,7 @@ */ package org.hibernate.envers.query.criteria; import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -40,10 +41,11 @@ public class LogicalAuditExpression implements AuditCriterion { this.op = op; } - public void addToQuery(AuditConfiguration verCfg, String entityName, QueryBuilder qb, Parameters parameters) { + public void addToQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { Parameters opParameters = parameters.addSubParameters(op); - lhs.addToQuery(verCfg, entityName, qb, opParameters.addSubParameters("and")); - rhs.addToQuery(verCfg, entityName, qb, opParameters.addSubParameters("and")); + lhs.addToQuery(verCfg, versionsReader, entityName, qb, opParameters.addSubParameters("and")); + rhs.addToQuery(verCfg, versionsReader, entityName, qb, opParameters.addSubParameters("and")); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotAuditExpression.java index 90c7ad506c..85e1ac5dbf 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotAuditExpression.java @@ -22,7 +22,9 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -36,7 +38,8 @@ public class NotAuditExpression implements AuditCriterion { this.criterion = criterion; } - public void addToQuery(AuditConfiguration verCfg, String entityName, QueryBuilder qb, Parameters parameters) { - criterion.addToQuery(verCfg, entityName, qb, parameters.addNegatedParameters()); + public void addToQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + criterion.addToQuery(verCfg, versionsReader, entityName, qb, parameters.addNegatedParameters()); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotNullAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotNullAuditExpression.java index 2186143b04..6c6f1806d3 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotNullAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NotNullAuditExpression.java @@ -22,9 +22,11 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.RelationDescription; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -38,8 +40,9 @@ public class NotNullAuditExpression implements AuditCriterion { this.propertyNameGetter = propertyNameGetter; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); RelationDescription relatedEntity = CriteriaTools.getRelatedEntity(auditCfg, entityName, propertyName); if (relatedEntity == null) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NullAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NullAuditExpression.java index 09c001cada..d18a183bf4 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NullAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/NullAuditExpression.java @@ -22,9 +22,11 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.RelationDescription; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -38,8 +40,9 @@ public class NullAuditExpression implements AuditCriterion { this.propertyNameGetter = propertyNameGetter; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); RelationDescription relatedEntity = CriteriaTools.getRelatedEntity(auditCfg, entityName, propertyName); if (relatedEntity == null) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/PropertyAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/PropertyAuditExpression.java index d0e4e6b2d0..e3347c38f9 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/PropertyAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/PropertyAuditExpression.java @@ -22,8 +22,10 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -41,8 +43,9 @@ public class PropertyAuditExpression implements AuditCriterion { this.op = op; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); CriteriaTools.checkPropertyNotARelation(auditCfg, entityName, propertyName); CriteriaTools.checkPropertyNotARelation(auditCfg, entityName, otherPropertyName); parameters.addWhere(propertyName, op, otherPropertyName); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RelatedAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RelatedAuditExpression.java index 4d6ba626b9..6d2bb74b9e 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RelatedAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RelatedAuditExpression.java @@ -22,10 +22,12 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.RelationDescription; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -43,8 +45,9 @@ public class RelatedAuditExpression implements AuditCriterion { this.equals = equals; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); RelationDescription relatedEntity = CriteriaTools.getRelatedEntity(auditCfg, entityName, propertyName); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RevisionTypeAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RevisionTypeAuditExpression.java index e3412d975d..ece2c48226 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RevisionTypeAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/RevisionTypeAuditExpression.java @@ -21,9 +21,10 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ - package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -39,7 +40,8 @@ public class RevisionTypeAuditExpression implements AuditCriterion { this.op = op; } - public void addToQuery(AuditConfiguration verCfg, String entityName, QueryBuilder qb, Parameters parameters) { + public void addToQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { parameters.addWhereWithParam(verCfg.getAuditEntCfg().getRevisionTypePropName(), op, value); } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/SimpleAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/SimpleAuditExpression.java index 4ecf44f0cf..121deef5b2 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/SimpleAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/SimpleAuditExpression.java @@ -22,10 +22,12 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.query.criteria; + import org.hibernate.envers.configuration.AuditConfiguration; import org.hibernate.envers.entities.RelationDescription; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.query.property.PropertyNameGetter; +import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; @@ -43,8 +45,9 @@ public class SimpleAuditExpression implements AuditCriterion { this.op = op; } - public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) { - String propertyName = propertyNameGetter.get(auditCfg); + public void addToQuery(AuditConfiguration auditCfg, AuditReaderImplementor versionsReader, String entityName, + QueryBuilder qb, Parameters parameters) { + String propertyName = CriteriaTools.determinePropertyName( auditCfg, versionsReader, entityName, propertyNameGetter ); RelationDescription relatedEntity = CriteriaTools.getRelatedEntity(auditCfg, entityName, propertyName); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/AbstractAuditQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/AbstractAuditQuery.java index bcc8a6a7c0..a3d8863a26 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/AbstractAuditQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/AbstractAuditQuery.java @@ -38,6 +38,7 @@ import org.hibernate.envers.entities.EntityInstantiator; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.query.AuditQuery; import org.hibernate.envers.query.criteria.AuditCriterion; +import org.hibernate.envers.query.criteria.CriteriaTools; import org.hibernate.envers.query.order.AuditOrder; import org.hibernate.envers.query.projection.AuditProjection; import org.hibernate.envers.reader.AuditReaderImplementor; @@ -64,7 +65,7 @@ public abstract class AbstractAuditQuery implements AuditQuery { protected boolean hasOrder; protected final AuditConfiguration verCfg; - private final AuditReaderImplementor versionsReader; + protected final AuditReaderImplementor versionsReader; protected AbstractAuditQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, Class cls) { @@ -129,15 +130,16 @@ public abstract class AbstractAuditQuery implements AuditQuery { public AuditQuery addProjection(AuditProjection projection) { Triple projectionData = projection.getData(verCfg); hasProjection = true; - qb.addProjection(projectionData.getFirst(), projectionData.getSecond(), projectionData.getThird()); + String propertyName = CriteriaTools.determinePropertyName( verCfg, versionsReader, entityName, projectionData.getSecond() ); + qb.addProjection(projectionData.getFirst(), propertyName, projectionData.getThird()); return this; } public AuditQuery addOrder(AuditOrder order) { hasOrder = true; - Pair orderData = order.getData(verCfg); - qb.addOrder(orderData.getFirst(), orderData.getSecond()); + String propertyName = CriteriaTools.determinePropertyName( verCfg, versionsReader, entityName, orderData.getFirst() ); + qb.addOrder(propertyName, orderData.getSecond()); return this; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java index b40e3579d9..d7b168b486 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java @@ -96,7 +96,7 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery { // all specified conditions for (AuditCriterion criterion : criterions) { - criterion.addToQuery(verCfg, entityName, qb, qb.getRootParameters()); + criterion.addToQuery(verCfg, versionsReader, entityName, qb, qb.getRootParameters()); } Query query = buildQuery(); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesModifiedAtRevisionQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesModifiedAtRevisionQuery.java index 4d64d97166..391df73533 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesModifiedAtRevisionQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesModifiedAtRevisionQuery.java @@ -45,7 +45,7 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery { // all specified conditions for (AuditCriterion criterion : criterions) { - criterion.addToQuery(verCfg, entityName, qb, qb.getRootParameters()); + criterion.addToQuery(verCfg, versionsReader, entityName, qb, qb.getRootParameters()); } Query query = buildQuery(); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/RevisionsOfEntityQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/RevisionsOfEntityQuery.java index 4c7edc64fd..a33eb533c0 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/RevisionsOfEntityQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/RevisionsOfEntityQuery.java @@ -96,7 +96,7 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery { // all specified conditions, transformed for (AuditCriterion criterion : criterions) { - criterion.addToQuery(verCfg, entityName, qb, qb.getRootParameters()); + criterion.addToQuery(verCfg, versionsReader, entityName, qb, qb.getRootParameters()); } if (!hasProjection && !hasOrder) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/property/OriginalIdPropertyName.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/property/OriginalIdPropertyName.java index 83bdbe7b49..58044944d1 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/property/OriginalIdPropertyName.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/property/OriginalIdPropertyName.java @@ -21,13 +21,16 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ - package org.hibernate.envers.query.property; + import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.query.criteria.AuditId; /** * Used for specifying restrictions on the identifier. - * TODO: idPropertyName should be read basing on auditCfg + entityName + * + * @deprecated To be removed together with {@link AuditId#count(String)} in version 5.0. + * * @author Adam Warski (adam at warski dot org) */ public class OriginalIdPropertyName implements PropertyNameGetter { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/revisioninfo/DefaultRevisionInfoGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/revisioninfo/DefaultRevisionInfoGenerator.java index f55eddf6d7..bda9b8788b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/revisioninfo/DefaultRevisionInfoGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/revisioninfo/DefaultRevisionInfoGenerator.java @@ -24,6 +24,7 @@ package org.hibernate.envers.revisioninfo; import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; import java.util.Date; import org.hibernate.MappingException; @@ -34,6 +35,7 @@ import org.hibernate.envers.RevisionType; import org.hibernate.envers.entities.PropertyData; import org.hibernate.envers.synchronization.SessionCacheCleaner; import org.hibernate.envers.tools.reflection.ReflectionTools; +import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.Setter; /** @@ -61,11 +63,13 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator { if (!listenerClass.equals(RevisionListener.class)) { // This is not the default value. try { - listener = listenerClass.newInstance(); + listener = (RevisionListener) ReflectHelper.getDefaultConstructor(listenerClass).newInstance(); } catch (InstantiationException e) { throw new MappingException(e); } catch (IllegalAccessException e) { throw new MappingException(e); + } catch (InvocationTargetException e) { + throw new MappingException(e); } } else { // Default listener - none @@ -83,7 +87,7 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator { public Object generate() { Object revisionInfo; try { - revisionInfo = revisionInfoClass.newInstance(); + revisionInfo = ReflectHelper.getDefaultConstructor(revisionInfoClass).newInstance(); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java index 16fcc14656..3a84d1ecb3 100755 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java @@ -1,99 +1,104 @@ -package org.hibernate.envers.strategy; -import java.io.Serializable; - -import org.hibernate.Session; -import org.hibernate.envers.configuration.AuditConfiguration; -import org.hibernate.envers.configuration.GlobalConfiguration; -import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData; -import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; -import org.hibernate.envers.entities.mapper.relation.MiddleIdData; -import org.hibernate.envers.tools.query.QueryBuilder; - -/** - * Behaviours of different audit strategy for populating audit data. - * - * @author Stephanie Pau - * @author Adam Warski (adam at warski dot org) - */ -public interface AuditStrategy { - /** - * Perform the persistence of audited data for regular entities. - * - * @param session Session, which can be used to persist the data. - * @param entityName Name of the entity, in which the audited change happens - * @param auditCfg Audit configuration - * @param id Id of the entity. - * @param data Audit data to persist - * @param revision Current revision data - */ - void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data, - Object revision); - - /** - * Perform the persistence of audited data for collection ("middle") entities. - * - * @param session Session, which can be used to persist the data. - * @param auditCfg Audit configuration - * @param persistentCollectionChangeData Collection change data to be persisted. - * @param revision Current revision data - */ - void performCollectionChange(Session session, AuditConfiguration auditCfg, - PersistentCollectionChangeData persistentCollectionChangeData, Object revision); - - - /** - * Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a two-entity relation. - * This WHERE clause depends on the AuditStrategy, as follows: - *
    - *
  • For {@link DefaultAuditStrategy} a subquery is created: - *

    e.revision = (SELECT max(...) ...)

    - *
  • - *
  • for {@link ValidityAuditStrategy} the revision-end column is used: - *

    e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null)

    - *
  • - *
- * - * @param globalCfg the {@link GlobalConfiguration} - * @param rootQueryBuilder the {@link QueryBuilder} that will be updated - * @param revisionProperty property of the revision column - * @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy}) - * @param addAlias {@code boolean} indicator if a left alias is needed - * @param idData id-information for the two-entity relation (only used for {@link DefaultAuditStrategy}) - * @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy}) - * @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy}) - * @param alias1 an alias used for subquery (only used for {@link ValidityAuditStrategy}) - * @param alias2 an alias used for subquery (only used for {@link ValidityAuditStrategy}) - */ - void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder, - String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData idData, - String revisionPropertyPath, String originalIdPropertyName, String alias1, String alias2); - - /** - * Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a middle-entity - * association. This WHERE clause depends on the AuditStrategy, as follows: - *
    - *
  • For {@link DefaultAuditStrategy} a subquery is created: - *

    e.revision = (SELECT max(...) ...)

    - *
  • - *
  • for {@link ValidityAuditStrategy} the revision-end column is used: - *

    e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null)

    - *
  • - *
- * - * @param rootQueryBuilder the {@link QueryBuilder} that will be updated - * @param revisionProperty property of the revision column - * @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy}) - * @param addAlias {@code boolean} indicator if a left alias is needed - * @param referencingIdData id-information for the middle-entity association (only used for {@link DefaultAuditStrategy}) - * @param versionsMiddleEntityName name of the middle-entity - * @param eeOriginalIdPropertyPath name of the id property (only used for {@link ValidityAuditStrategy}) - * @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy}) - * @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy}) - * @param componentDatas information about the middle-entity relation - */ - void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, String revisionProperty, - String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData, - String versionsMiddleEntityName, String eeOriginalIdPropertyPath, String revisionPropertyPath, - String originalIdPropertyName, MiddleComponentData... componentDatas); - -} +package org.hibernate.envers.strategy; + +import java.io.Serializable; + +import org.hibernate.Session; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.configuration.GlobalConfiguration; +import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData; +import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; +import org.hibernate.envers.entities.mapper.relation.MiddleIdData; +import org.hibernate.envers.tools.query.QueryBuilder; + +/** + * Behaviours of different audit strategy for populating audit data. + * + * @author Stephanie Pau + * @author Adam Warski (adam at warski dot org) + */ +public interface AuditStrategy { + /** + * Perform the persistence of audited data for regular entities. + * + * @param session Session, which can be used to persist the data. + * @param entityName Name of the entity, in which the audited change happens + * @param auditCfg Audit configuration + * @param id Id of the entity. + * @param data Audit data to persist + * @param revision Current revision data + */ + void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data, + Object revision); + + /** + * Perform the persistence of audited data for collection ("middle") entities. + * + * @param session Session, which can be used to persist the data. + * @param entityName Name of the entity, in which the audited change happens. + * @param propertyName The name of the property holding the {@link PersistentCollection}. + * @param auditCfg Audit configuration + * @param persistentCollectionChangeData Collection change data to be persisted. + * @param revision Current revision data + */ + void performCollectionChange(Session session, String entityName, String propertyName, AuditConfiguration auditCfg, + PersistentCollectionChangeData persistentCollectionChangeData, Object revision); + + + /** + * Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a two-entity relation. + * This WHERE clause depends on the AuditStrategy, as follows: + *
    + *
  • For {@link DefaultAuditStrategy} a subquery is created: + *

    e.revision = (SELECT max(...) ...)

    + *
  • + *
  • for {@link ValidityAuditStrategy} the revision-end column is used: + *

    e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null)

    + *
  • + *
+ * + * @param globalCfg the {@link GlobalConfiguration} + * @param rootQueryBuilder the {@link QueryBuilder} that will be updated + * @param revisionProperty property of the revision column + * @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy}) + * @param addAlias {@code boolean} indicator if a left alias is needed + * @param idData id-information for the two-entity relation (only used for {@link DefaultAuditStrategy}) + * @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy}) + * @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy}) + * @param alias1 an alias used for subquery (only used for {@link ValidityAuditStrategy}) + * @param alias2 an alias used for subquery (only used for {@link ValidityAuditStrategy}) + */ + void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder, + String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData idData, + String revisionPropertyPath, String originalIdPropertyName, String alias1, String alias2); + + /** + * Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a middle-entity + * association. This WHERE clause depends on the AuditStrategy, as follows: + *
    + *
  • For {@link DefaultAuditStrategy} a subquery is created: + *

    e.revision = (SELECT max(...) ...)

    + *
  • + *
  • for {@link ValidityAuditStrategy} the revision-end column is used: + *

    e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null)

    + *
  • + *
+ * + * @param rootQueryBuilder the {@link QueryBuilder} that will be updated + * @param revisionProperty property of the revision column + * @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy}) + * @param addAlias {@code boolean} indicator if a left alias is needed + * @param referencingIdData id-information for the middle-entity association (only used for {@link DefaultAuditStrategy}) + * @param versionsMiddleEntityName name of the middle-entity + * @param eeOriginalIdPropertyPath name of the id property (only used for {@link ValidityAuditStrategy}) + * @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy}) + * @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy}) + * @param alias1 an alias used for subqueries (only used for {@link DefaultAuditStrategy}) + * @param componentDatas information about the middle-entity relation + */ + void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, String revisionProperty, + String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData, + String versionsMiddleEntityName, String eeOriginalIdPropertyPath, String revisionPropertyPath, + String originalIdPropertyName, String alias1, MiddleComponentData... componentDatas); + +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java index d8c25e5943..5959c75d94 100755 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java @@ -34,7 +34,7 @@ public class DefaultAuditStrategy implements AuditStrategy { sessionCacheCleaner.scheduleAuditDataRemoval(session, data); } - public void performCollectionChange(Session session, AuditConfiguration auditCfg, + public void performCollectionChange(Session session, String entityName, String propertyName, AuditConfiguration auditCfg, PersistentCollectionChangeData persistentCollectionChangeData, Object revision) { session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData()); sessionCacheCleaner.scheduleAuditDataRemoval(session, persistentCollectionChangeData.getData()); @@ -66,7 +66,7 @@ public class DefaultAuditStrategy implements AuditStrategy { public void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData, String versionsMiddleEntityName, String eeOriginalIdPropertyPath, String revisionPropertyPath, - String originalIdPropertyName, MiddleComponentData... componentDatas) { + String originalIdPropertyName, String alias1, MiddleComponentData... componentDatas) { Parameters rootParameters = rootQueryBuilder.getRootParameters(); // SELECT max(ee2.revision) FROM middleEntity ee2 @@ -80,7 +80,7 @@ public class DefaultAuditStrategy implements AuditStrategy { String ee2OriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS_DEF_AUD_STR + "." + originalIdPropertyName; referencingIdData.getPrefixedMapper().addIdsEqualToQuery(maxEeRevQbParameters, eeOriginalIdPropertyPath, ee2OriginalIdPropertyPath); for (MiddleComponentData componentData : componentDatas) { - componentData.getComponentMapper().addMiddleEqualToQuery(maxEeRevQbParameters, eeOriginalIdPropertyPath, ee2OriginalIdPropertyPath); + componentData.getComponentMapper().addMiddleEqualToQuery(maxEeRevQbParameters, eeOriginalIdPropertyPath, alias1, ee2OriginalIdPropertyPath, MIDDLE_ENTITY_ALIAS_DEF_AUD_STR); } // add subquery to rootParameters diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java index 9d286cce1e..6e2c6cc808 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java @@ -1,5 +1,8 @@ package org.hibernate.envers.strategy; +import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS; +import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER; + import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; @@ -10,11 +13,10 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.jboss.logging.Logger; - import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.RevisionType; import org.hibernate.envers.configuration.AuditConfiguration; @@ -32,15 +34,14 @@ import org.hibernate.event.spi.AutoFlushEventListener; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; import org.hibernate.jdbc.ReturningWork; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.property.Getter; import org.hibernate.sql.Update; +import org.hibernate.type.CollectionType; +import org.hibernate.type.ComponentType; import org.hibernate.type.Type; - -import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS; -import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER; +import org.jboss.logging.Logger; /** * Audit strategy which persists and retrieves audit information using a validity algorithm, based on the @@ -162,7 +163,7 @@ public class ValidityAuditStrategy implements AuditStrategy { new ReturningWork() { @Override public Integer execute(Connection connection) throws SQLException { - PreparedStatement preparedStatement = connection.prepareStatement( updateSql ); + PreparedStatement preparedStatement = sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( updateSql ); try { int index = 1; @@ -197,15 +198,10 @@ public class ValidityAuditStrategy implements AuditStrategy { // where REVEND is null // nothing to bind.... - return preparedStatement.executeUpdate(); + return sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( preparedStatement ); } finally { - try { - preparedStatement.close(); - } - catch (SQLException e) { - log.debug( "Could not release prepared statement : " + e.getMessage() ); - } + sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().release( preparedStatement ); } } } @@ -241,21 +237,38 @@ public class ValidityAuditStrategy implements AuditStrategy { } @SuppressWarnings({"unchecked"}) - public void performCollectionChange(Session session, AuditConfiguration auditCfg, + public void performCollectionChange(Session session, String entityName, String propertyName, AuditConfiguration auditCfg, PersistentCollectionChangeData persistentCollectionChangeData, Object revision) { final QueryBuilder qb = new QueryBuilder(persistentCollectionChangeData.getEntityName(), MIDDLE_ENTITY_ALIAS); - // Adding a parameter for each id component, except the rev number final String originalIdPropName = auditCfg.getAuditEntCfg().getOriginalIdPropName(); - final Map originalId = (Map) persistentCollectionChangeData.getData().get( - originalIdPropName); + final Map originalId = (Map) persistentCollectionChangeData.getData().get(originalIdPropName); + final String revisionFieldName = auditCfg.getAuditEntCfg().getRevisionFieldName(); + final String revisionTypePropName = auditCfg.getAuditEntCfg().getRevisionTypePropName(); + + // Adding a parameter for each id component, except the rev number and type. for (Map.Entry originalIdEntry : originalId.entrySet()) { - if (!auditCfg.getAuditEntCfg().getRevisionFieldName().equals(originalIdEntry.getKey())) { + if (!revisionFieldName.equals(originalIdEntry.getKey()) && !revisionTypePropName.equals(originalIdEntry.getKey())) { qb.getRootParameters().addWhereWithParam(originalIdPropName + "." + originalIdEntry.getKey(), true, "=", originalIdEntry.getValue()); } } + final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) session ).getFactory(); + final Type propertyType = sessionFactory.getEntityPersister( entityName ).getPropertyType( propertyName ); + if ( propertyType.isCollectionType() ) { + CollectionType collectionPropertyType = (CollectionType) propertyType; + // Handling collection of components. + if ( collectionPropertyType.getElementType( sessionFactory ) instanceof ComponentType ) { + // Adding restrictions to compare data outside of primary key. + for ( Map.Entry dataEntry : persistentCollectionChangeData.getData().entrySet() ) { + if ( !originalIdPropName.equals( dataEntry.getKey() ) ) { + qb.getRootParameters().addWhereWithParam( dataEntry.getKey(), true, "=", dataEntry.getValue() ); + } + } + } + } + addEndRevisionNullRestriction(auditCfg, qb.getRootParameters()); final List l = qb.toQuery(session).setLockOptions(LockOptions.UPGRADE).list(); @@ -287,7 +300,7 @@ public class ValidityAuditStrategy implements AuditStrategy { public void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData, String versionsMiddleEntityName, String eeOriginalIdPropertyPath, String revisionPropertyPath, - String originalIdPropertyName, MiddleComponentData... componentDatas) { + String originalIdPropertyName, String alias1, MiddleComponentData... componentDatas) { Parameters rootParameters = rootQueryBuilder.getRootParameters(); addRevisionRestriction(rootParameters, revisionProperty, revisionEndProperty, addAlias); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java index f28270f74b..f4f73786f3 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java @@ -76,6 +76,7 @@ public class CollectionChangeWorkUnit extends AbstractAuditWorkUnit implements A } public AuditWorkUnit merge(ModWorkUnit second) { + mergeCollectionModifiedData(second.getData()); return second; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java index 927cd2fb0f..5b2454b0fa 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.synchronization.work; + import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -36,12 +37,19 @@ import org.hibernate.persister.entity.EntityPersister; */ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit { private final Map data; - private final boolean changes; + private final boolean changes; + + private final EntityPersister entityPersister; + private final Object[] oldState; + private final Object[] newState; public ModWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg, Serializable id, EntityPersister entityPersister, Object[] newState, Object[] oldState) { super(sessionImplementor, entityName, verCfg, id, RevisionType.MOD); + this.entityPersister = entityPersister; + this.oldState = oldState; + this.newState = newState; data = new HashMap(); changes = verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data, entityPersister.getPropertyNames(), newState, oldState); @@ -66,7 +74,12 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit } public AuditWorkUnit merge(ModWorkUnit second) { - return second; + // In case of multiple subsequent flushes within single transaction, modification flags need to be + // recalculated against initial and final state of the given entity. + return new ModWorkUnit( + second.sessionImplementor, second.getEntityName(), second.verCfg, second.id, + second.entityPersister, second.newState, this.oldState + ); } public AuditWorkUnit merge(DelWorkUnit second) { @@ -74,6 +87,7 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit } public AuditWorkUnit merge(CollectionChangeWorkUnit second) { + second.mergeCollectionModifiedData(data); return this; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java index d3103eb319..4728690502 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java @@ -53,7 +53,7 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im this.referencingPropertyName = referencingPropertyName; collectionChanges = auditCfg.getEntCfg().get(getEntityName()).getPropertyMapper() - .mapCollectionChanges(referencingPropertyName, collection, snapshot, id); + .mapCollectionChanges(sessionImplementor, referencingPropertyName, collection, snapshot, id); } public PersistentCollectionChangeWorkUnit(SessionImplementor sessionImplementor, String entityName, @@ -83,7 +83,7 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im ((Map) persistentCollectionChangeData.getData().get(entitiesCfg.getOriginalIdPropName())) .put(entitiesCfg.getRevisionFieldName(), revisionData); - auditStrategy.performCollectionChange(session, verCfg, persistentCollectionChangeData, revisionData); + auditStrategy.performCollectionChange(session, getEntityName(), referencingPropertyName, verCfg, persistentCollectionChangeData, revisionData); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/tools/StringTools.java b/hibernate-envers/src/main/java/org/hibernate/envers/tools/StringTools.java index 8d2abf6611..09f955064e 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/tools/StringTools.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/tools/StringTools.java @@ -32,6 +32,10 @@ public class StringTools { return s == null || "".equals(s); } + public static boolean isEmpty(Object o) { + return o == null || "".equals(o); + } + /** * @param s String, from which to get the last component. * @return The last component of the dot-separated string s. For example, for a string diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/tools/reflection/ReflectionTools.java b/hibernate-envers/src/main/java/org/hibernate/envers/tools/reflection/ReflectionTools.java index 23ab1e19ce..da7908aa1d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/tools/reflection/ReflectionTools.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/tools/reflection/ReflectionTools.java @@ -22,10 +22,11 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.tools.reflection; +import static org.hibernate.envers.tools.Pair.make; + import java.util.Map; import org.hibernate.envers.entities.PropertyData; -import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.tools.Pair; import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap; import org.hibernate.property.Getter; @@ -33,8 +34,6 @@ import org.hibernate.property.PropertyAccessor; import org.hibernate.property.PropertyAccessorFactory; import org.hibernate.property.Setter; -import static org.hibernate.envers.tools.Pair.make; - /** * @author Adam Warski (adam at warski dot org) */ @@ -48,14 +47,6 @@ public class ReflectionTools { ConcurrentReferenceHashMap.ReferenceType.SOFT, ConcurrentReferenceHashMap.ReferenceType.SOFT); - public static Class loadClass(String name) { - try { - return Thread.currentThread().getContextClassLoader().loadClass(name); - } catch (ClassNotFoundException e) { - throw new AuditException(e); - } - } - private static PropertyAccessor getAccessor(String accessorType) { return PropertyAccessorFactory.getPropertyAccessor(accessorType); } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/BaseEnversJPAFunctionalTestCase.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/BaseEnversJPAFunctionalTestCase.java index 1856dba36d..242761b26c 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/BaseEnversJPAFunctionalTestCase.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/BaseEnversJPAFunctionalTestCase.java @@ -129,7 +129,7 @@ public abstract class BaseEnversJPAFunctionalTestCase extends AbstractEnversTest settings.put( "org.hibernate.envers.audit_strategy", getAuditStrategy() ); } - if ( ! isAudit() ) { + if ( ! autoRegisterListeners() ) { settings.put( EnversIntegrator.AUTO_REGISTER, "false" ); } @@ -214,7 +214,7 @@ public abstract class BaseEnversJPAFunctionalTestCase extends AbstractEnversTest return null; } - protected boolean isAudit() { + protected boolean autoRegisterListeners() { return true; } @@ -224,11 +224,12 @@ public abstract class BaseEnversJPAFunctionalTestCase extends AbstractEnversTest entityManagerFactory.close(); } } + @After @SuppressWarnings({ "UnusedDeclaration" }) public void releaseUnclosedEntityManagers() { releaseUnclosedEntityManager( this.em ); - auditReader =null; + auditReader = null; for ( EntityManager isolatedEm : isolatedEms ) { releaseUnclosedEntityManager( isolatedEm ); } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/PrimitiveTestEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/PrimitiveTestEntity.java index 314f30b110..df383049a7 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/PrimitiveTestEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/PrimitiveTestEntity.java @@ -53,7 +53,7 @@ public class PrimitiveTestEntity { public PrimitiveTestEntity(Integer id, int numVal1, int number2) { this.id = id; this.numVal1 = numVal1; - this.numVal2 = numVal2; + this.numVal2 = number2; } public Integer getId() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/StrTestNoProxyEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/StrTestNoProxyEntity.java new file mode 100644 index 0000000000..7a1ef0fb09 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/StrTestNoProxyEntity.java @@ -0,0 +1,98 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Proxy; +import org.hibernate.envers.Audited; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Table(name = "STR_TEST_NP") +@Proxy(lazy = false) +public class StrTestNoProxyEntity implements Serializable { + @Id + @GeneratedValue + private Integer id; + + @Audited + private String str; + + public StrTestNoProxyEntity() { + } + + public StrTestNoProxyEntity(String str, Integer id) { + this.str = str; + this.id = id; + } + + public StrTestNoProxyEntity(String str) { + this.str = str; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getStr() { + return str; + } + + public void setStr(String str) { + this.str = str; + } + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof StrTestNoProxyEntity ) ) return false; + + StrTestNoProxyEntity that = (StrTestNoProxyEntity) o; + + if ( id != null ? !id.equals( that.id ) : that.id != null ) return false; + if ( str != null ? !str.equals( that.str ) : that.str != null ) return false; + + return true; + } + + public int hashCode() { + int result = ( id != null ? id.hashCode() : 0 ); + result = 31 * result + ( str != null ? str.hashCode() : 0 ); + return result; + } + + public String toString() { + return "STNPE(id = " + id + ", str = " + str + ")"; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableListEntity1.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableListEntity1.java new file mode 100644 index 0000000000..8b93599693 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableListEntity1.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.collection; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OrderColumn; +import javax.persistence.Table; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.components.Component3; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@Entity +@Table(name = "EmbListEnt1") +@Audited +public class EmbeddableListEntity1 { + @Id + @GeneratedValue + private Integer id; + + private String otherData; + + @ElementCollection + @OrderColumn + @CollectionTable(name = "EmbListEnt1_list") + private List componentList = new ArrayList(); + + public EmbeddableListEntity1() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getComponentList() { + return componentList; + } + + public void setComponentList(List componentList) { + this.componentList = componentList; + } + + public String getOtherData() { + return otherData; + } + + public void setOtherData(String otherData) { + this.otherData = otherData; + } + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof EmbeddableListEntity1 ) ) return false; + + EmbeddableListEntity1 that = (EmbeddableListEntity1) o; + + if ( id != null ? !id.equals( that.id ) : that.id != null ) return false; + + return true; + } + + public int hashCode() { + return ( id != null ? id.hashCode() : 0 ); + } + + public String toString() { + return "ELE1(id = " + id + ", otherData = " + otherData + ", componentList = " + componentList + ")"; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableListEntity2.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableListEntity2.java new file mode 100644 index 0000000000..82029ab6fa --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableListEntity2.java @@ -0,0 +1,94 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.collection; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OrderColumn; +import javax.persistence.Table; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.components.relations.ManyToOneEagerComponent; + +/** + * Embeddable list with components encapsulating many-to-one relation (referencing some entity). + * + * @author thiagolrc + */ +@Entity +@Table(name = "EmbListEnt2") +@Audited +public class EmbeddableListEntity2 { + @Id + @GeneratedValue + private Integer id; + + @ElementCollection + @OrderColumn + @CollectionTable(name = "EmbListEnt2_list") + private List componentList = new ArrayList(); + + public EmbeddableListEntity2() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getComponentList() { + return componentList; + } + + public void setComponentList(List componentList) { + this.componentList = componentList; + } + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof EmbeddableListEntity2 ) ) return false; + + EmbeddableListEntity2 that = (EmbeddableListEntity2) o; + + if ( id != null ? !id.equals( that.id ) : that.id != null ) return false; + + return true; + } + + public int hashCode() { + return ( id != null ? id.hashCode() : 0 ); + } + + public String toString() { + return "ELE2(id = " + id + ", componentList = " + componentList + ")"; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableMapEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableMapEntity.java new file mode 100644 index 0000000000..7e59ce4cce --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableMapEntity.java @@ -0,0 +1,93 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.collection; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.components.Component3; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@Entity +@Table(name = "EmbMapEnt") +public class EmbeddableMapEntity { + @Id + @GeneratedValue + private Integer id; + + @Audited + @ElementCollection + @CollectionTable(name = "EmbMapEnt_map") + @MapKeyColumn(nullable = false) // NOT NULL for Sybase + private Map componentMap; + + public EmbeddableMapEntity() { + componentMap = new HashMap(); + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Map getComponentMap() { + return componentMap; + } + + public void setComponentMap(Map strings) { + this.componentMap = strings; + } + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof EmbeddableMapEntity ) ) return false; + + EmbeddableMapEntity that = (EmbeddableMapEntity) o; + + if ( id != null ? !id.equals( that.id ) : that.id != null ) return false; + + return true; + } + + public int hashCode() { + return ( id != null ? id.hashCode() : 0 ); + } + + public String toString() { + return "EME(id = " + id + ", componentMap = " + componentMap + ")"; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableSetEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableSetEntity.java new file mode 100644 index 0000000000..e5a09afadf --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/EmbeddableSetEntity.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.collection; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.components.Component3; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@Entity +@Table(name = "EmbSetEnt") +@Audited +public class EmbeddableSetEntity { + @Id + @GeneratedValue + private Integer id; + + @ElementCollection + @CollectionTable(name = "EmbSetEnt_set") + private Set componentSet = new HashSet(); + + public EmbeddableSetEntity() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Set getComponentSet() { + return componentSet; + } + + public void setComponentSet(Set componentSet) { + this.componentSet = componentSet; + } + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof EmbeddableSetEntity ) ) return false; + + EmbeddableSetEntity that = (EmbeddableSetEntity) o; + + if ( id != null ? !id.equals( that.id ) : that.id != null ) return false; + + return true; + } + + public int hashCode() { + return ( id != null ? id.hashCode() : 0 ); + } + + public String toString() { + return "ESE(id = " + id + ", componentSet = " + componentSet + ')'; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java index ccc4956748..443b143ec5 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/collection/MultipleCollectionEntity.java @@ -82,6 +82,20 @@ public class MultipleCollectionEntity { refEntities2.remove(refEntity2); } + /** + * For test purpose only. + */ + public void setRefEntities1(List refEntities1) { + this.refEntities1 = refEntities1; + } + + /** + * For test purpose only. + */ + public void setRefEntities2(List refEntities2) { + this.refEntities2 = refEntities2; + } + @Override public String toString() { return "MultipleCollectionEntity [id=" + id + ", text=" + text diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component3.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component3.java new file mode 100644 index 0000000000..ac6f956b8c --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component3.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.components; + +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embeddable; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; + +/** + * The {@link #nonAuditedComponent} is ignored in {@link #hashCode()} + * and {@link #equals(Object)} since it's not audited. + * + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@Embeddable +@Audited +public class Component3 { + private String str1; + + @AttributeOverrides({ + @AttributeOverride(name = "key", column = @Column(name = "audComp_key")), + @AttributeOverride(name = "value", column = @Column(name = "audComp_value")), + @AttributeOverride(name = "description", column = @Column(name = "audComp_description")) + }) + private Component4 auditedComponent; + + @NotAudited + @AttributeOverrides({ + @AttributeOverride(name = "key", column = @Column(name = "notAudComp_key")), + @AttributeOverride(name = "value", column = @Column(name = "notAudComp_value")), + @AttributeOverride(name = "description", column = @Column(name = "notAudComp_description")) + }) + private Component4 nonAuditedComponent; + + public Component3() { + } + + public Component3(String str1, Component4 auditedComponent, Component4 nonAuditedComponent) { + this.str1 = str1; + this.auditedComponent = auditedComponent; + this.nonAuditedComponent = nonAuditedComponent; + } + + public String getStr1() { + return str1; + } + + public void setStr1(String str1) { + this.str1 = str1; + } + + public Component4 getAuditedComponent() { + return auditedComponent; + } + + public void setAuditedComponent(Component4 auditedComponent) { + this.auditedComponent = auditedComponent; + } + + public Component4 getNonAuditedComponent() { + return nonAuditedComponent; + } + + public void setNonAuditedComponent(Component4 nonAuditedComponent) { + this.nonAuditedComponent = nonAuditedComponent; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( auditedComponent == null ) ? 0 : auditedComponent.hashCode() ); + result = prime * result + ( ( str1 == null ) ? 0 : str1.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) return true; + if ( !( obj instanceof Component3 ) ) return false; + + Component3 other = (Component3) obj; + + if ( auditedComponent != null ? !auditedComponent.equals( other.auditedComponent ) : other.auditedComponent != null ) return false; + if ( str1 != null ? !str1.equals( other.str1 ) : other.str1 != null ) return false; + + return true; + } + + @Override + public String toString() { + return "Component3[str1 = " + str1 + ", auditedComponent = " + + auditedComponent + ", nonAuditedComponent = " + + nonAuditedComponent + "]"; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component4.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component4.java new file mode 100644 index 0000000000..20924c577d --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/Component4.java @@ -0,0 +1,104 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.components; + +import javax.persistence.Embeddable; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@Embeddable +@Audited +public class Component4 { + private String key; + private String value; + + @NotAudited + private String description; + + public Component4() { + } + + public Component4(String key, String value, String description) { + this.key = key; + this.value = value; + this.description = description; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( description == null ) ? 0 : description.hashCode() ); + result = prime * result + ( ( key == null ) ? 0 : key.hashCode() ); + result = prime * result + ( ( value == null ) ? 0 : value.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) return true; + if ( !( obj instanceof Component4 ) ) return false; + + Component4 other = (Component4) obj; + + if ( description != null ? !description.equals( other.description ) : other.description != null ) return false; + if ( key != null ? !key.equals( other.key ) : other.key != null ) return false; + if ( value != null ? !value.equals( other.value ) : other.value != null ) return false; + + return true; + } + + @Override + public String toString() { + return "Component4[key = " + key + ", value = " + value + ", description = " + description + "]"; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/relations/ManyToOneComponent.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/relations/ManyToOneComponent.java index dd8c92593a..2dd415791f 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/relations/ManyToOneComponent.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/relations/ManyToOneComponent.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.test.entities.components.relations; + import javax.persistence.Embeddable; import javax.persistence.ManyToOne; import javax.persistence.Table; diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/relations/ManyToOneEagerComponent.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/relations/ManyToOneEagerComponent.java new file mode 100644 index 0000000000..9de66b66ca --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/components/relations/ManyToOneEagerComponent.java @@ -0,0 +1,94 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.components.relations; + +import javax.persistence.Embeddable; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.StrTestNoProxyEntity; + +/** + * Do not mark as {@link Audited}. Should be implicitly treated as audited when part of audited entity. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Embeddable +@Table(name = "ManyToOneEagerComp") +public class ManyToOneEagerComponent { + @ManyToOne(fetch = FetchType.EAGER) + private StrTestNoProxyEntity entity; + + private String data; + + public ManyToOneEagerComponent(StrTestNoProxyEntity entity, String data) { + this.entity = entity; + this.data = data; + } + + public ManyToOneEagerComponent() { + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public StrTestNoProxyEntity getEntity() { + return entity; + } + + public void setEntity(StrTestNoProxyEntity entity) { + this.entity = entity; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof ManyToOneEagerComponent ) ) return false; + + ManyToOneEagerComponent that = (ManyToOneEagerComponent) o; + + if ( data != null ? !data.equals( that.data ) : that.data != null ) return false; + if ( entity != null ? !entity.equals( that.entity ) : that.entity != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = entity != null ? entity.hashCode() : 0; + result = 31 * result + ( data != null ? data.hashCode() : 0 ); + return result; + } + + public String toString() { + return "ManyToOneEagerComponent(data = " + data + ")"; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/ids/UnusualIdNamingEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/ids/UnusualIdNamingEntity.java new file mode 100644 index 0000000000..98f72c4024 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/ids/UnusualIdNamingEntity.java @@ -0,0 +1,68 @@ +package org.hibernate.envers.test.entities.ids; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.envers.Audited; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class UnusualIdNamingEntity implements Serializable { + @Id + private String uniqueField; + + private String variousData; + + public UnusualIdNamingEntity() { + } + + public UnusualIdNamingEntity(String uniqueField, String variousData) { + this.uniqueField = uniqueField; + this.variousData = variousData; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof UnusualIdNamingEntity) ) return false; + + UnusualIdNamingEntity that = (UnusualIdNamingEntity) o; + + if ( uniqueField != null ? !uniqueField.equals( that.uniqueField ) : that.uniqueField != null ) return false; + if ( variousData != null ? !variousData.equals( that.variousData ) : that.variousData != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = uniqueField != null ? uniqueField.hashCode() : 0; + result = 31 * result + ( variousData != null ? variousData.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "UnusualIdNamingEntity(uniqueField = " + uniqueField + ", variousData = " + variousData + ")"; + } + + public String getUniqueField() { + return uniqueField; + } + + public void setUniqueField(String uniqueField) { + this.uniqueField = uniqueField; + } + + public String getVariousData() { + return variousData; + } + + public void setVariousData(String variousData) { + this.variousData = variousData; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefEdOneToOne.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefEdOneToOne.java index eb294b3424..8f483bd275 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefEdOneToOne.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefEdOneToOne.java @@ -28,6 +28,7 @@ import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; +import javax.persistence.Table; import org.hibernate.annotations.Proxy; import org.hibernate.envers.Audited; @@ -39,6 +40,8 @@ import org.hibernate.envers.NotAudited; @Entity @Audited @Proxy(lazy=false) +// Class name is too long of an identifier for Oracle. +@Table(name = "EdOneToOne") public final class BidirectionalEagerAnnotationRefEdOneToOne { /** * ID column. diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefIngOneToOne.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefIngOneToOne.java index 71e50b714a..3431d29ddd 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefIngOneToOne.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/entities/onetoone/BidirectionalEagerAnnotationRefIngOneToOne.java @@ -30,6 +30,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; +import javax.persistence.Table; import org.hibernate.annotations.Proxy; import org.hibernate.envers.Audited; @@ -40,6 +41,8 @@ import org.hibernate.envers.Audited; @Entity @Audited @Proxy(lazy=false) +//Class name is too long of an identifier for Oracle. +@Table(name = "IngOneToOne") public final class BidirectionalEagerAnnotationRefIngOneToOne { /** * ID column. diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/DetachedMultipleCollectionChangeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/DetachedMultipleCollectionChangeTest.java index 2b7e1045d7..1c0e524d28 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/DetachedMultipleCollectionChangeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/DetachedMultipleCollectionChangeTest.java @@ -11,6 +11,7 @@ import javax.transaction.TransactionManager; import org.junit.Test; +import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.envers.RevisionType; import org.hibernate.envers.enhanced.SequenceIdRevisionEntity; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; @@ -18,6 +19,7 @@ import org.hibernate.envers.test.Priority; import org.hibernate.envers.test.entities.collection.MultipleCollectionEntity; import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity1; import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity2; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.jta.TestingJtaBootstrap; import org.hibernate.testing.jta.TestingJtaPlatformImpl; @@ -32,6 +34,8 @@ import static org.junit.Assert.assertNotNull; * @author Erik-Berndt Scheper */ @TestForIssue(jiraKey = "HHH-6349") +@SkipForDialect(value = Oracle8iDialect.class, + comment = "Oracle does not support identity key generation") public class DetachedMultipleCollectionChangeTest extends BaseEnversJPAFunctionalTestCase { private TransactionManager tm = null; diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/EnumSet.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/EnumSet.java index 27ca0970c9..842c1396fe 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/EnumSet.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/EnumSet.java @@ -115,11 +115,15 @@ public class EnumSet extends BaseEnversJPAFunctionalTestCase { @TestForIssue( jiraKey = "HHH-7780" ) public void testEnumRepresentation() { EntityManager entityManager = getEntityManager(); - List enums1 = entityManager.createNativeQuery( "SELECT enums1 FROM EnumSetEntity_enums1_AUD ORDER BY rev ASC" ).getResultList(); - List enums2 = entityManager.createNativeQuery( "SELECT enums2 FROM EnumSetEntity_enums2_AUD ORDER BY rev ASC" ).getResultList(); + List enums1 = entityManager.createNativeQuery( "SELECT enums1 FROM EnumSetEntity_enums1_AUD ORDER BY rev ASC" ).getResultList(); + List enums2 = entityManager.createNativeQuery( "SELECT enums2 FROM EnumSetEntity_enums2_AUD ORDER BY rev ASC" ).getResultList(); entityManager.close(); Assert.assertEquals( Arrays.asList( "X", "Y", "X" ), enums1 ); - Assert.assertEquals( Arrays.asList( 0 ), enums2 ); + + Assert.assertEquals( 1, enums2.size() ); + Object enum2 = enums2.get( 0 ); + // Compare the Strings to account for, as an example, Oracle returning a BigDecimal instead of an int. + Assert.assertEquals( "0", enum2.toString() ); } } \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/BasicEmbeddableCollection.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/BasicEmbeddableCollection.java new file mode 100644 index 0000000000..6531197709 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/BasicEmbeddableCollection.java @@ -0,0 +1,130 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.collection.embeddable; + +import java.util.Arrays; +import javax.persistence.EntityManager; + +import junit.framework.Assert; +import org.junit.Test; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.testing.TestForIssue; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue( jiraKey = "HHH-6613" ) +public class BasicEmbeddableCollection extends BaseEnversJPAFunctionalTestCase { + private int id = -1; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { DarkCharacter.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 - empty element collection + em.getTransaction().begin(); + DarkCharacter darkCharacter = new DarkCharacter( 1, 1 ); + em.persist( darkCharacter ); + em.getTransaction().commit(); + + id = darkCharacter.getId(); + + // Revision 2 - adding collection element + em.getTransaction().begin(); + darkCharacter = em.find( DarkCharacter.class, darkCharacter.getId() ); + darkCharacter.getNames().add( new Name( "Action", "Hank" ) ); + darkCharacter = em.merge( darkCharacter ); + em.getTransaction().commit(); + + // Revision 3 - adding another collection element + em.getTransaction().begin(); + darkCharacter = em.find( DarkCharacter.class, darkCharacter.getId() ); + darkCharacter.getNames().add( new Name( "Green", "Lantern" ) ); + darkCharacter = em.merge( darkCharacter ); + em.getTransaction().commit(); + + // Revision 4 - removing single collection element + em.getTransaction().begin(); + darkCharacter = em.find( DarkCharacter.class, darkCharacter.getId() ); + darkCharacter.getNames().remove( new Name( "Action", "Hank" ) ); + darkCharacter = em.merge( darkCharacter ); + em.getTransaction().commit(); + + // Revision 5 - removing all collection elements + em.getTransaction().begin(); + darkCharacter = em.find( DarkCharacter.class, darkCharacter.getId() ); + darkCharacter.getNames().clear(); + darkCharacter = em.merge( darkCharacter ); + em.getTransaction().commit(); + + em.close(); + } + + @Test + public void testRevisionsCount() { + Assert.assertEquals( Arrays.asList( 1, 2, 3, 4, 5 ), getAuditReader().getRevisions( DarkCharacter.class, id ) ); + } + + @Test + public void testHistoryOfCharacter() { + DarkCharacter darkCharacter = new DarkCharacter( id, 1 ); + + DarkCharacter ver1 = getAuditReader().find( DarkCharacter.class, id, 1 ); + + Assert.assertEquals( darkCharacter, ver1 ); + Assert.assertEquals( 0, ver1.getNames().size() ); + + darkCharacter.getNames().add( new Name( "Action", "Hank" ) ); + DarkCharacter ver2 = getAuditReader().find( DarkCharacter.class, id, 2 ); + + Assert.assertEquals( darkCharacter, ver2 ); + Assert.assertEquals( darkCharacter.getNames(), ver2.getNames() ); + + darkCharacter.getNames().add( new Name( "Green", "Lantern" ) ); + DarkCharacter ver3 = getAuditReader().find( DarkCharacter.class, id, 3 ); + + Assert.assertEquals( darkCharacter, ver3 ); + Assert.assertEquals( darkCharacter.getNames(), ver3.getNames() ); + + darkCharacter.getNames().remove( new Name( "Action", "Hank" ) ); + DarkCharacter ver4 = getAuditReader().find( DarkCharacter.class, id, 4 ); + + Assert.assertEquals( darkCharacter, ver4 ); + Assert.assertEquals( darkCharacter.getNames(), ver4.getNames() ); + + darkCharacter.getNames().clear(); + DarkCharacter ver5 = getAuditReader().find( DarkCharacter.class, id, 5 ); + + Assert.assertEquals( darkCharacter, ver5 ); + Assert.assertEquals( darkCharacter.getNames(), ver5.getNames() ); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/DarkCharacter.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/DarkCharacter.java new file mode 100644 index 0000000000..79db3ff12d --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/DarkCharacter.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.collection.embeddable; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.envers.Audited; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class DarkCharacter implements Serializable { + @Id + private int id; + + @ElementCollection + private Set names = new HashSet(); + + private int kills; + + public DarkCharacter() { + } + + public DarkCharacter(int id, int kills) { + this.id = id; + this.kills = kills; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof DarkCharacter ) ) return false; + + DarkCharacter character = (DarkCharacter) o; + + if ( id != character.id ) return false; + if ( kills != character.kills ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + kills; + return result; + } + + @Override + public String toString() { + return "DarkCharacter(id = " + id + ", kills = " + kills + ")"; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getKills() { + return kills; + } + + public void setKills(int kills) { + this.kills = kills; + } + + public Set getNames() { + return names; + } + + public void setNames(Set names) { + this.names = names; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableList1.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableList1.java new file mode 100644 index 0000000000..49d207fae8 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableList1.java @@ -0,0 +1,117 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.collection.embeddable; + +import java.util.Arrays; +import java.util.Collections; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.collection.EmbeddableListEntity1; +import org.hibernate.envers.test.entities.components.Component3; +import org.hibernate.envers.test.entities.components.Component4; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@TestForIssue( jiraKey = "HHH-6613" ) +public class EmbeddableList1 extends BaseEnversJPAFunctionalTestCase { + private Integer ele1_id = null; + + private final Component4 c4_1 = new Component4( "c41", "c41_value", "c41_description" ); + private final Component4 c4_2 = new Component4( "c42", "c42_value2", "c42_description" ); + private final Component3 c3_1 = new Component3( "c31", c4_1, c4_2 ); + private final Component3 c3_2 = new Component3( "c32", c4_1, c4_2 ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EmbeddableListEntity1.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + EmbeddableListEntity1 ele1 = new EmbeddableListEntity1(); + + // Revision 1 (ele1: initially 1 element in both collections) + em.getTransaction().begin(); + ele1.getComponentList().add( c3_1 ); + em.persist( ele1 ); + em.getTransaction().commit(); + + // Revision (still 1) (ele1: removing non-existing element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().remove( c3_2 ); + em.getTransaction().commit(); + + // Revision 2 (ele1: adding one element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().add( c3_2 ); + em.getTransaction().commit(); + + // Revision 3 (ele1: adding one existing element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().add( c3_1 ); + em.getTransaction().commit(); + + // Revision 4 (ele1: removing one existing element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().remove( c3_2 ); + em.getTransaction().commit(); + + ele1_id = ele1.getId(); + + em.close(); + } + + @Test + public void testRevisionsCounts() { + assertEquals( Arrays.asList( 1, 2, 3, 4 ), getAuditReader().getRevisions( EmbeddableListEntity1.class, ele1_id ) ); + } + + @Test + public void testHistoryOfEle1() { + EmbeddableListEntity1 rev1 = getAuditReader().find( EmbeddableListEntity1.class, ele1_id, 1 ); + EmbeddableListEntity1 rev2 = getAuditReader().find( EmbeddableListEntity1.class, ele1_id, 2 ); + EmbeddableListEntity1 rev3 = getAuditReader().find( EmbeddableListEntity1.class, ele1_id, 3 ); + EmbeddableListEntity1 rev4 = getAuditReader().find( EmbeddableListEntity1.class, ele1_id, 4 ); + + assertEquals( Collections.singletonList( c3_1 ), rev1.getComponentList() ); + assertEquals( Arrays.asList( c3_1, c3_2 ), rev2.getComponentList() ); + assertEquals( Arrays.asList( c3_1, c3_2, c3_1 ), rev3.getComponentList() ); + assertEquals( Arrays.asList( c3_1, c3_1 ), rev4.getComponentList() ); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableList2.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableList2.java new file mode 100644 index 0000000000..4d325b8504 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableList2.java @@ -0,0 +1,233 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.collection.embeddable; + +import java.util.Arrays; +import java.util.Date; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.StrTestNoProxyEntity; +import org.hibernate.envers.test.entities.collection.EmbeddableListEntity2; +import org.hibernate.envers.test.entities.components.relations.ManyToOneEagerComponent; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Checks if many-to-one relations inside an embedded component list are being audited. + * + * @author thiagolrc + */ +@TestForIssue( jiraKey = "HHH-6613" ) +public class EmbeddableList2 extends BaseEnversJPAFunctionalTestCase { + private Integer ele_id1 = null; + + private StrTestNoProxyEntity entity1 = new StrTestNoProxyEntity( "strTestEntity1" ); + private StrTestNoProxyEntity entity2 = new StrTestNoProxyEntity( "strTestEntity2" ); + private StrTestNoProxyEntity entity3 = new StrTestNoProxyEntity( "strTestEntity3" ); + private StrTestNoProxyEntity entity4 = new StrTestNoProxyEntity( "strTestEntity3" ); + private StrTestNoProxyEntity entity4Copy = null; + + private ManyToOneEagerComponent manyToOneComponent1 = new ManyToOneEagerComponent( entity1, "dataComponent1" ); + private ManyToOneEagerComponent manyToOneComponent2 = new ManyToOneEagerComponent( entity2, "dataComponent2" ); + private ManyToOneEagerComponent manyToOneComponent4 = new ManyToOneEagerComponent( entity4, "dataComponent4" ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EmbeddableListEntity2.class, StrTestNoProxyEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 (ele1: saving a list with 1 many-to-one component) + em.getTransaction().begin(); + EmbeddableListEntity2 ele1 = new EmbeddableListEntity2(); + em.persist( entity1 ); //persisting the entities referenced by the components + em.persist( entity2 ); + ele1.getComponentList().add( manyToOneComponent1 ); + em.persist( ele1 ); + em.getTransaction().commit(); + ele_id1 = ele1.getId(); + + // Revision 2 (ele1: changing the component) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity2.class, ele1.getId() ); + ele1.getComponentList().clear(); + ele1.getComponentList().add( manyToOneComponent2 ); + em.getTransaction().commit(); + + //Revision 3 (ele1: putting back the many-to-one component to the list) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity2.class, ele1.getId() ); + ele1.getComponentList().add( manyToOneComponent1 ); + em.getTransaction().commit(); + + // Revision 4 (ele1: changing the component's entity) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity2.class, ele1.getId() ); + em.persist( entity3 ); + ele1.getComponentList().get( ele1.getComponentList().indexOf( manyToOneComponent2 ) ).setEntity( entity3 ); + ele1.getComponentList().get( ele1.getComponentList().indexOf( manyToOneComponent2 ) ).setData( "dataComponent3" ); + em.getTransaction().commit(); + + // Revision 5 (ele1: adding a new many-to-one component) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity2.class, ele1.getId() ); + em.persist( entity4 ); + entity4Copy = new StrTestNoProxyEntity( entity4.getStr(), entity4.getId() ); + ele1.getComponentList().add( manyToOneComponent4 ); + em.getTransaction().commit(); + + // Revision 6 (ele1: changing the component's entity properties) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity2.class, ele1.getId() ); + ele1.getComponentList().get( ele1.getComponentList().indexOf( manyToOneComponent4 ) ).getEntity().setStr( "sat4" ); + em.getTransaction().commit(); + + // Revision 7 (ele1: removing component) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity2.class, ele1.getId() ); + ele1.getComponentList().remove( ele1.getComponentList().indexOf( manyToOneComponent4 ) ); + em.getTransaction().commit(); + + // Revision 8 (ele1: removing all) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity2.class, ele1.getId() ); + em.remove( ele1 ); + em.getTransaction().commit(); + + em.close(); + } + + @Test + public void testRevisionsCounts() { + assertEquals( + Arrays.asList( 1, 2, 3, 4, 5, 7, 8 ), + getAuditReader().getRevisions( EmbeddableListEntity2.class, ele_id1 ) + ); + assertEquals( + Arrays.asList( 1 ), getAuditReader().getRevisions( StrTestNoProxyEntity.class, entity1.getId() ) + ); + assertEquals( + Arrays.asList( 1 ), getAuditReader().getRevisions( StrTestNoProxyEntity.class, entity2.getId() ) + ); + assertEquals( + Arrays.asList( 4 ), getAuditReader().getRevisions( StrTestNoProxyEntity.class, entity3.getId() ) + ); + assertEquals( + Arrays.asList( 5, 6 ), + getAuditReader().getRevisions( StrTestNoProxyEntity.class, entity4.getId() ) + ); + } + + @Test + public void testManyToOneComponentList() { + // Revision 1: many-to-one component1 in the list + EmbeddableListEntity2 rev1 = getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 1 ); + assertNotNull( "Revision not found", rev1 ); + assertTrue( "The component collection was not audited", rev1.getComponentList().size() > 0 ); + assertEquals( + "The component primitive property was not audited", + "dataComponent1", rev1.getComponentList().get( 0 ).getData() + ); + assertEquals( + "The component manyToOne reference was not audited", + entity1, rev1.getComponentList().get( 0 ).getEntity() + ); + } + + @Test + public void testHistoryOfEle1() { + // Revision 1: many-to-one component in the list + assertEquals( + Arrays.asList( new ManyToOneEagerComponent( entity1, "dataComponent1" ) ), + getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 1 ).getComponentList() + ); + + // Revision 2: many-to-one component in the list + assertEquals( + Arrays.asList( new ManyToOneEagerComponent( entity2, "dataComponent2" ) ), + getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 2 ).getComponentList() + ); + + // Revision 3: two many-to-one components in the list + assertEquals( + Arrays.asList( + new ManyToOneEagerComponent( entity2, "dataComponent2" ), + new ManyToOneEagerComponent( entity1, "dataComponent1" ) + ), + getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 3 ).getComponentList() + ); + + // Revision 4: second component edited and first one in the list + assertEquals( + Arrays.asList( + new ManyToOneEagerComponent( entity3, "dataComponent3" ), + new ManyToOneEagerComponent( entity1, "dataComponent1" ) + ), + getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 4 ).getComponentList() + ); + + // Revision 5: fourth component added in the list + assertEquals( + Arrays.asList( + new ManyToOneEagerComponent( entity3, "dataComponent3" ), + new ManyToOneEagerComponent( entity1, "dataComponent1" ), + new ManyToOneEagerComponent( entity4Copy, "dataComponent4" ) + ), + getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 5 ).getComponentList() + ); + + // Revision 6: changing fourth component property + assertEquals( + Arrays.asList( + new ManyToOneEagerComponent( entity3, "dataComponent3" ), + new ManyToOneEagerComponent( entity1, "dataComponent1" ), + new ManyToOneEagerComponent( entity4, "dataComponent4" ) + ), + getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 6 ).getComponentList() + ); + + // Revision 7: removing component number four + assertEquals( + Arrays.asList( + new ManyToOneEagerComponent( entity3, "dataComponent3" ), + new ManyToOneEagerComponent( entity1, "dataComponent1" ) + ), + getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 7 ).getComponentList() + ); + + assertNull( getAuditReader().find( EmbeddableListEntity2.class, ele_id1, 8 ) ); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableMap.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableMap.java new file mode 100644 index 0000000000..2a2895dd77 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableMap.java @@ -0,0 +1,136 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.collection.embeddable; + +import java.util.Arrays; +import java.util.Collections; + +import javax.persistence.EntityManager; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.collection.EmbeddableMapEntity; +import org.hibernate.envers.test.entities.components.Component3; +import org.hibernate.envers.test.entities.components.Component4; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.testing.TestForIssue; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@TestForIssue( jiraKey = "HHH-6613" ) +public class EmbeddableMap extends BaseEnversJPAFunctionalTestCase { + private Integer eme1_id = null; + private Integer eme2_id = null; + + private final Component4 c4_1 = new Component4( "c41", "c41_value", "c41_description" ); + private final Component4 c4_2 = new Component4( "c42", "c42_value2", "c42_description" ); + private final Component3 c3_1 = new Component3( "c31", c4_1, c4_2 ); + private final Component3 c3_2 = new Component3( "c32", c4_1, c4_2 ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EmbeddableMapEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + EmbeddableMapEntity eme1 = new EmbeddableMapEntity(); + EmbeddableMapEntity eme2 = new EmbeddableMapEntity(); + + // Revision 1 (eme1: initialy empty, eme2: initialy 1 mapping) + em.getTransaction().begin(); + eme2.getComponentMap().put( "1", c3_1 ); + em.persist( eme1 ); + em.persist( eme2 ); + em.getTransaction().commit(); + + // Revision 2 (eme1: adding 2 mappings, eme2: no changes) + em.getTransaction().begin(); + eme1 = em.find( EmbeddableMapEntity.class, eme1.getId() ); + eme2 = em.find( EmbeddableMapEntity.class, eme2.getId() ); + eme1.getComponentMap().put( "1", c3_1 ); + eme1.getComponentMap().put( "2", c3_2 ); + em.getTransaction().commit(); + + // Revision 3 (eme1: removing an existing mapping, eme2: replacing a value) + em.getTransaction().begin(); + eme1 = em.find( EmbeddableMapEntity.class, eme1.getId() ); + eme2 = em.find( EmbeddableMapEntity.class, eme2.getId() ); + eme1.getComponentMap().remove( "1" ); + eme2.getComponentMap().put( "1", c3_2 ); + em.getTransaction().commit(); + + // No revision (eme1: removing a non-existing mapping, eme2: replacing with the same value) + em.getTransaction().begin(); + eme1 = em.find( EmbeddableMapEntity.class, eme1.getId() ); + eme2 = em.find( EmbeddableMapEntity.class, eme2.getId() ); + eme1.getComponentMap().remove( "3" ); + eme2.getComponentMap().put( "1", c3_2 ); + em.getTransaction().commit(); + + eme1_id = eme1.getId(); + eme2_id = eme2.getId(); + + em.close(); + } + + @Test + public void testRevisionsCounts() { + Assert.assertEquals( Arrays.asList( 1, 2, 3 ), getAuditReader().getRevisions( EmbeddableMapEntity.class, eme1_id ) ); + Assert.assertEquals( Arrays.asList( 1, 3 ), getAuditReader().getRevisions( EmbeddableMapEntity.class, eme2_id ) ); + } + + @Test + public void testHistoryOfEme1() { + EmbeddableMapEntity rev1 = getAuditReader().find( EmbeddableMapEntity.class, eme1_id, 1 ); + EmbeddableMapEntity rev2 = getAuditReader().find( EmbeddableMapEntity.class, eme1_id, 2 ); + EmbeddableMapEntity rev3 = getAuditReader().find( EmbeddableMapEntity.class, eme1_id, 3 ); + EmbeddableMapEntity rev4 = getAuditReader().find( EmbeddableMapEntity.class, eme1_id, 4 ); + + Assert.assertEquals( Collections.EMPTY_MAP, rev1.getComponentMap() ); + Assert.assertEquals( TestTools.makeMap( "1", c3_1, "2", c3_2 ), rev2.getComponentMap() ); + Assert.assertEquals( TestTools.makeMap( "2", c3_2 ), rev3.getComponentMap() ); + Assert.assertEquals( TestTools.makeMap( "2", c3_2 ), rev4.getComponentMap() ); + } + + @Test + public void testHistoryOfEme2() { + EmbeddableMapEntity rev1 = getAuditReader().find( EmbeddableMapEntity.class, eme2_id, 1 ); + EmbeddableMapEntity rev2 = getAuditReader().find( EmbeddableMapEntity.class, eme2_id, 2 ); + EmbeddableMapEntity rev3 = getAuditReader().find( EmbeddableMapEntity.class, eme2_id, 3 ); + EmbeddableMapEntity rev4 = getAuditReader().find( EmbeddableMapEntity.class, eme2_id, 4 ); + + Assert.assertEquals( TestTools.makeMap( "1", c3_1 ), rev1.getComponentMap() ); + Assert.assertEquals( TestTools.makeMap( "1", c3_1 ), rev2.getComponentMap() ); + Assert.assertEquals( TestTools.makeMap( "1", c3_2 ), rev3.getComponentMap() ); + Assert.assertEquals( TestTools.makeMap( "1", c3_2 ), rev4.getComponentMap() ); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableSet.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableSet.java new file mode 100644 index 0000000000..32c93332db --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/EmbeddableSet.java @@ -0,0 +1,116 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.collection.embeddable; + +import java.util.Arrays; +import java.util.Collections; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.collection.EmbeddableSetEntity; +import org.hibernate.envers.test.entities.components.Component3; +import org.hibernate.envers.test.entities.components.Component4; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; + +/** + * @author Kristoffer Lundberg (kristoffer at cambio dot se) + */ +@TestForIssue( jiraKey = "HHH-6613" ) +public class EmbeddableSet extends BaseEnversJPAFunctionalTestCase { + private Integer ese1_id = null; + + private final Component4 c4_1 = new Component4( "c41", "c41_value", "c41_description" ); + private final Component4 c4_2 = new Component4( "c42", "c42_value2", "c42_description" ); + private final Component3 c3_1 = new Component3( "c31", c4_1, c4_2 ); + private final Component3 c3_2 = new Component3( "c32", c4_1, c4_2 ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EmbeddableSetEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + EmbeddableSetEntity ese1 = new EmbeddableSetEntity(); + + // Revision 1 (ese1: initially 1 element in both collections) + em.getTransaction().begin(); + ese1.getComponentSet().add( c3_1 ); + em.persist( ese1 ); + em.getTransaction().commit(); + + // Revision (still 1) (ese1: removing non-existing element) + em.getTransaction().begin(); + ese1 = em.find( EmbeddableSetEntity.class, ese1.getId() ); + ese1.getComponentSet().remove( c3_2 ); + em.getTransaction().commit(); + + // Revision 2 (ese1: adding one element) + em.getTransaction().begin(); + ese1 = em.find( EmbeddableSetEntity.class, ese1.getId() ); + ese1.getComponentSet().add( c3_2 ); + em.getTransaction().commit(); + + // Revision 3 (ese1: adding one existing element) + em.getTransaction().begin(); + ese1 = em.find( EmbeddableSetEntity.class, ese1.getId() ); + ese1.getComponentSet().add( c3_1 ); + em.getTransaction().commit(); + + // Revision 4 (ese1: removing one existing element) + em.getTransaction().begin(); + ese1 = em.find( EmbeddableSetEntity.class, ese1.getId() ); + ese1.getComponentSet().remove( c3_2 ); + em.getTransaction().commit(); + + ese1_id = ese1.getId(); + + em.close(); + } + + @Test + public void testRevisionsCounts() { + assertEquals( Arrays.asList( 1, 2, 3 ), getAuditReader().getRevisions( EmbeddableSetEntity.class, ese1_id ) ); + } + + @Test + public void testHistoryOfEse1() { + EmbeddableSetEntity rev1 = getAuditReader().find( EmbeddableSetEntity.class, ese1_id, 1 ); + EmbeddableSetEntity rev2 = getAuditReader().find( EmbeddableSetEntity.class, ese1_id, 2 ); + EmbeddableSetEntity rev3 = getAuditReader().find( EmbeddableSetEntity.class, ese1_id, 3 ); + + assertEquals( Collections.singleton( c3_1 ), rev1.getComponentSet() ); + assertEquals( TestTools.makeSet( c3_1, c3_2 ), rev2.getComponentSet() ); + assertEquals( TestTools.makeSet( c3_1 ), rev3.getComponentSet() ); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/Name.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/Name.java new file mode 100644 index 0000000000..c9581814ff --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/embeddable/Name.java @@ -0,0 +1,87 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.collection.embeddable; + +import java.io.Serializable; +import javax.persistence.Embeddable; + +import org.hibernate.envers.Audited; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Embeddable +@Audited +public class Name implements Serializable { + private String firstName; + private String lastName; + + public Name() { + } + + public Name(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof Name ) ) return false; + + Name name = (Name) o; + if ( firstName != null ? !firstName.equals( name.firstName ) : name.firstName != null ) return false; + if ( lastName != null ? !lastName.equals( name.lastName ) : name.lastName != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = firstName != null ? firstName.hashCode() : 0; + result = 31 * result + ( lastName != null ? lastName.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Name(firstName = " + firstName + ", lastName = " + lastName + ")"; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/EnumTypeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/EnumTypeTest.java index 9af5bff98e..df82aec2bf 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/EnumTypeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/EnumTypeTest.java @@ -42,6 +42,11 @@ public class EnumTypeTest extends BaseEnversJPAFunctionalTestCase { Assert.assertNotNull( values ); Assert.assertEquals( 1, values.size() ); - Assert.assertArrayEquals( new Object[] { "X", 0 }, values.get( 0 ) ); + Object[] results = values.get( 0 ); + Assert.assertEquals( 2, results.length ); + Assert.assertEquals( "X", results[0] ); + // Compare the Strings to account for, as an example, Oracle + // returning a BigDecimal instead of an int. + Assert.assertEquals( "0", results[1] + "" ); } } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserType.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserType.java new file mode 100644 index 0000000000..b848d8960a --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserType.java @@ -0,0 +1,146 @@ +package org.hibernate.envers.test.integration.customtype; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.usertype.UserType; + +/** + * Custom type used to persist binary representation of Java object in the database. + * Spans over two columns - one storing text representation of Java class name and the second one + * containing binary data. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class ObjectUserType implements UserType { + private static final int[] TYPES = new int[] { Types.VARCHAR, Types.BLOB }; + + @Override + public int[] sqlTypes() { + return TYPES; + } + + @Override + public Class returnedClass() { + return Object.class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + if ( x == y ) return true; + if ( x == null || y == null ) return false; + return x.equals( y ); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return x.hashCode(); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + final String type = rs.getString( names[0] ); // For debug purpose. + return convertInputStreamToObject( rs.getBinaryStream( names[1] ) ); + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) + throws HibernateException, SQLException { + if ( value == null ) { + st.setNull( index, TYPES[0] ); + st.setNull( index + 1, TYPES[1] ); + } + else { + st.setString( index, value.getClass().getName() ); + st.setBinaryStream( index + 1, convertObjectToInputStream( value ) ); + } + } + + private InputStream convertObjectToInputStream(Object value) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = null; + try { + objectOutputStream = new ObjectOutputStream( byteArrayOutputStream ); + objectOutputStream.writeObject( value ); + objectOutputStream.flush(); + return new ByteArrayInputStream( byteArrayOutputStream.toByteArray() ); + } + catch ( IOException e ) { + throw new RuntimeException( e ); + } + finally { + closeQuietly( objectOutputStream ); + } + } + + private Object convertInputStreamToObject(InputStream inputStream) { + ObjectInputStream objectInputStream = null; + try { + objectInputStream = new ObjectInputStream( inputStream ); + return objectInputStream.readObject(); + } + catch ( Exception e ) { + throw new RuntimeException( e ); + } + finally { + closeQuietly( objectInputStream ); + } + } + + private void closeQuietly(OutputStream stream) { + if ( stream != null ) { + try { + stream.close(); + } + catch ( IOException e ) { + } + } + } + + private void closeQuietly(InputStream stream) { + if ( stream != null ) { + try { + stream.close(); + } + catch ( IOException e ) { + } + } + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + return value; // Persisting only immutable types. + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(Object value) throws HibernateException { + return (Serializable) value; + } + + @Override + public Object assemble(Serializable cached, Object owner) throws HibernateException { + return cached; + } + + @Override + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return original; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserTypeEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserTypeEntity.java new file mode 100644 index 0000000000..f1f39e4eb2 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserTypeEntity.java @@ -0,0 +1,94 @@ +package org.hibernate.envers.test.integration.customtype; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.Columns; +import org.hibernate.annotations.Type; +import org.hibernate.envers.Audited; + +/** + * Entity encapsulating {@link Object} property which concrete type may change during subsequent updates. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class ObjectUserTypeEntity implements Serializable { + @Id + @GeneratedValue + private int id; + + private String buildInType; + + @Type(type = "org.hibernate.envers.test.integration.customtype.ObjectUserType") + @Columns(columns = { @Column(name = "OBJ_TYPE"), @Column(name = "OBJ_VALUE") }) + private Object userType; + + public ObjectUserTypeEntity() { + } + + public ObjectUserTypeEntity(String buildInType, Object userType) { + this.buildInType = buildInType; + this.userType = userType; + } + + public ObjectUserTypeEntity(int id, String buildInType, Object userType) { + this.id = id; + this.buildInType = buildInType; + this.userType = userType; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof ObjectUserTypeEntity ) ) return false; + + ObjectUserTypeEntity that = (ObjectUserTypeEntity) o; + + if ( id != that.id ) return false; + if ( buildInType != null ? !buildInType.equals( that.buildInType ) : that.buildInType != null ) return false; + if ( userType != null ? !userType.equals( that.userType ) : that.userType != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + ( buildInType != null ? buildInType.hashCode() : 0 ); + result = 31 * result + ( userType != null ? userType.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "ObjectUserTypeEntity(id = " + id + ", buildInType = " + buildInType + ", userType = " + userType + ")"; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getBuildInType() { + return buildInType; + } + + public void setBuildInType(String buildInType) { + this.buildInType = buildInType; + } + + public Object getUserType() { + return userType; + } + + public void setUserType(Object userType) { + this.userType = userType; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserTypeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserTypeTest.java new file mode 100644 index 0000000000..45ddd3efd6 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/ObjectUserTypeTest.java @@ -0,0 +1,84 @@ +package org.hibernate.envers.test.integration.customtype; + +import java.util.Arrays; +import java.util.Map; +import javax.persistence.EntityManager; + +import org.junit.Assert; +import org.junit.Test; + +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-7870") +@RequiresDialect(Oracle8iDialect.class) +public class ObjectUserTypeTest extends BaseEnversJPAFunctionalTestCase { + private int id; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { ObjectUserTypeEntity.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( "org.hibernate.envers.store_data_at_delete", "true" ); + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 - add + em.getTransaction().begin(); + ObjectUserTypeEntity entity = new ObjectUserTypeEntity( "builtInType1", "stringUserType1" ); + em.persist( entity ); + em.getTransaction().commit(); + + id = entity.getId(); + + // Revision 2 - modify + em.getTransaction().begin(); + entity = em.find( ObjectUserTypeEntity.class, entity.getId() ); + entity.setUserType( 2 ); + entity = em.merge( entity ); + em.getTransaction().commit(); + + // Revision 3 - remove + em.getTransaction().begin(); + entity = em.find( ObjectUserTypeEntity.class, entity.getId() ); + em.remove( entity ); + em.getTransaction().commit(); + + em.close(); + } + + @Test + public void testRevisionCount() { + Assert.assertEquals( + Arrays.asList( 1, 2, 3 ), + getAuditReader().getRevisions( ObjectUserTypeEntity.class, id ) + ); + } + + @Test + public void testHistory() { + ObjectUserTypeEntity ver1 = new ObjectUserTypeEntity( id, "builtInType1", "stringUserType1" ); + ObjectUserTypeEntity ver2 = new ObjectUserTypeEntity( id, "builtInType1", 2 ); + + Assert.assertEquals( ver1, getAuditReader().find( ObjectUserTypeEntity.class, id, 1 ) ); + Assert.assertEquals( ver2, getAuditReader().find( ObjectUserTypeEntity.class, id, 2 ) ); + Assert.assertEquals( + ver2, + getAuditReader().createQuery().forRevisionsOfEntity( ObjectUserTypeEntity.class, true, true ).getResultList().get( 2 ) + ); // Checking delete state. + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/DateTestEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/DateTestEntity.java index d7a1d76d70..0ff1f570c8 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/DateTestEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/DateTestEntity.java @@ -50,7 +50,7 @@ public class DateTestEntity { public DateTestEntity(Integer id, Date date) { this.id = id; - this.dateValue = dateValue; + this.dateValue = date; } public Integer getId() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/ItemId.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/ItemId.java index 359e028fd6..1afa11765a 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/ItemId.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/ItemId.java @@ -18,7 +18,7 @@ public class ItemId implements Serializable { private Integer version; @ManyToOne - @JoinColumn(name = "producer") + @JoinColumn(name = "producer", nullable = false) // NOT NULL for Sybase private Producer producer; public ItemId() { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/ProtectedConstructorEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/ProtectedConstructorEntity.java new file mode 100644 index 0000000000..bc05df0635 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/ProtectedConstructorEntity.java @@ -0,0 +1,65 @@ +package org.hibernate.envers.test.integration.ids.protectedmodifier; + +import java.io.Serializable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +import org.hibernate.envers.Audited; + +@Entity +@Audited +public class ProtectedConstructorEntity implements Serializable { + @EmbeddedId + private WrappedStringId wrappedStringId; + + private String str1; + + @SuppressWarnings("unused") + protected ProtectedConstructorEntity() { + // For JPA. Protected access modifier is essential in terms of unit test. + } + + public ProtectedConstructorEntity(WrappedStringId wrappedStringId, String str1) { + this.wrappedStringId = wrappedStringId; + this.str1 = str1; + } + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof ProtectedConstructorEntity ) ) return false; + + ProtectedConstructorEntity that = (ProtectedConstructorEntity) o; + + if ( wrappedStringId != null ? !wrappedStringId.equals( that.wrappedStringId ) : that.wrappedStringId != null ) return false; + if ( str1 != null ? !str1.equals( that.str1 ) : that.str1 != null ) return false; + + return true; + } + + public int hashCode() { + int result = ( wrappedStringId != null ? wrappedStringId.hashCode() : 0 ); + result = 31 * result + ( str1 != null ? str1.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "ProtectedConstructorEntity(wrappedStringId = " + wrappedStringId + ", str1 = " + str1 + ")"; + } + + public WrappedStringId getWrappedStringId() { + return wrappedStringId; + } + + public void setWrappedStringId(WrappedStringId wrappedStringId) { + this.wrappedStringId = wrappedStringId; + } + + public String getStr1() { + return str1; + } + + public void setStr1(String str1) { + this.str1 = str1; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/ProtectedConstructorTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/ProtectedConstructorTest.java new file mode 100644 index 0000000000..7a8f69fe6d --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/ProtectedConstructorTest.java @@ -0,0 +1,42 @@ +package org.hibernate.envers.test.integration.ids.protectedmodifier; + +import java.util.Arrays; +import java.util.List; +import javax.persistence.EntityManager; + +import org.junit.Assert; +import org.junit.Test; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.testing.TestForIssue; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-7934") +public class ProtectedConstructorTest extends BaseEnversJPAFunctionalTestCase { + private final ProtectedConstructorEntity testEntity = new ProtectedConstructorEntity( new WrappedStringId( "embeddedStringId" ), "string" ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { WrappedStringId.class, ProtectedConstructorEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + // Revision 1 + EntityManager em = getEntityManager(); + em.getTransaction().begin(); + em.persist( testEntity ); + em.getTransaction().commit(); + em.close(); + } + + @Test + public void testAuditEntityInstantiation() { + List result = getAuditReader().createQuery().forEntitiesAtRevision( ProtectedConstructorEntity.class, 1 ).getResultList(); + Assert.assertEquals( Arrays.asList( testEntity ), result ); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/WrappedStringId.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/WrappedStringId.java new file mode 100644 index 0000000000..0c06d8b42f --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/protectedmodifier/WrappedStringId.java @@ -0,0 +1,33 @@ +package org.hibernate.envers.test.integration.ids.protectedmodifier; + +import java.io.Serializable; +import javax.persistence.Embeddable; + +@Embeddable +public class WrappedStringId implements Serializable { + String id; + + @SuppressWarnings("unused") + protected WrappedStringId() { + // For JPA. Protected access modifier is essential in terms of unit test. + } + + public WrappedStringId(String id) { + this.id = id; + } + + public String toString() { + return id; + } + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( o == null || getClass() != o.getClass() ) return false; + WrappedStringId that = (WrappedStringId) o; + return !( id != null ? !id.equals( that.id ) : that.id != null ); + } + + public int hashCode() { + return id != null ? id.hashCode() : 0; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedComponentCollection.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedComponentCollection.java new file mode 100644 index 0000000000..cfb4953b00 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedComponentCollection.java @@ -0,0 +1,102 @@ +package org.hibernate.envers.test.integration.modifiedflags; + +import java.util.List; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.collection.EmbeddableListEntity1; +import org.hibernate.envers.test.entities.components.Component3; +import org.hibernate.envers.test.entities.components.Component4; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; +import static org.hibernate.envers.test.tools.TestTools.extractRevisionNumbers; +import static org.hibernate.envers.test.tools.TestTools.makeList; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue( jiraKey = "HHH-6613" ) +public class HasChangedComponentCollection extends AbstractModifiedFlagsEntityTest { + private Integer ele1_id = null; + + private final Component4 c4_1 = new Component4( "c41", "c41_value", "c41_description" ); + private final Component4 c4_2 = new Component4( "c42", "c42_value2", "c42_description" ); + private final Component3 c3_1 = new Component3( "c31", c4_1, c4_2 ); + private final Component3 c3_2 = new Component3( "c32", c4_1, c4_2 ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { EmbeddableListEntity1.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 (ele1: initially 1 element in both collections) + em.getTransaction().begin(); + EmbeddableListEntity1 ele1 = new EmbeddableListEntity1(); + ele1.setOtherData( "data" ); + ele1.getComponentList().add( c3_1 ); + em.persist( ele1 ); + em.getTransaction().commit(); + + // Revision (still 1) (ele1: removing non-existing element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().remove( c3_2 ); + em.getTransaction().commit(); + + // Revision 2 (ele1: updating singular property and removing non-existing element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.setOtherData( "modified" ); + ele1.getComponentList().remove( c3_2 ); + ele1 = em.merge( ele1 ); + em.getTransaction().commit(); + + // Revision 3 (ele1: adding one element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().add( c3_2 ); + em.getTransaction().commit(); + + // Revision 4 (ele1: adding one existing element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().add( c3_1 ); + em.getTransaction().commit(); + + // Revision 5 (ele1: removing one existing element) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.getComponentList().remove( c3_2 ); + em.getTransaction().commit(); + + // Revision 6 (ele1: changing singular property only) + em.getTransaction().begin(); + ele1 = em.find( EmbeddableListEntity1.class, ele1.getId() ); + ele1.setOtherData( "another modification" ); + ele1 = em.merge( ele1 ); + em.getTransaction().commit(); + + ele1_id = ele1.getId(); + + em.close(); + } + + @Test + public void testHasChangedEle() { + List list = queryForPropertyHasChanged( EmbeddableListEntity1.class, ele1_id, "componentList" ); + assertEquals( 4, list.size() ); + assertEquals( makeList( 1, 3, 4, 5 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( EmbeddableListEntity1.class, ele1_id, "otherData" ); + assertEquals( 3, list.size() ); + assertEquals( makeList( 1, 2, 6 ), extractRevisionNumbers( list ) ); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedDetachedMultipleCollection.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedDetachedMultipleCollection.java new file mode 100644 index 0000000000..05602fba00 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedDetachedMultipleCollection.java @@ -0,0 +1,150 @@ +package org.hibernate.envers.test.integration.modifiedflags; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.collection.MultipleCollectionEntity; +import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity1; +import org.hibernate.envers.test.entities.collection.MultipleCollectionRefEntity2; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; +import static org.hibernate.envers.test.tools.TestTools.extractRevisionNumbers; +import static org.hibernate.envers.test.tools.TestTools.makeList; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-7437") +@SkipForDialect(value = Oracle8iDialect.class, + comment = "Oracle does not support identity key generation") +public class HasChangedDetachedMultipleCollection extends AbstractModifiedFlagsEntityTest { + private Long mce1Id = null; + private Long mce2Id = null; + private Long mcre1Id = null; + private Long mcre2Id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + MultipleCollectionEntity.class, MultipleCollectionRefEntity1.class, MultipleCollectionRefEntity2.class + }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 - addition. + em.getTransaction().begin(); + MultipleCollectionEntity mce1 = new MultipleCollectionEntity(); + mce1.setText( "MultipleCollectionEntity-1-1" ); + em.persist( mce1 ); // Persisting entity with empty collections. + em.getTransaction().commit(); + + mce1Id = mce1.getId(); + + // Revision 2 - update. + em.getTransaction().begin(); + mce1 = em.find( MultipleCollectionEntity.class, mce1.getId() ); + MultipleCollectionRefEntity1 mcre1 = new MultipleCollectionRefEntity1(); + mcre1.setText( "MultipleCollectionRefEntity1-1-1" ); + mcre1.setMultipleCollectionEntity( mce1 ); + mce1.addRefEntity1( mcre1 ); + em.persist( mcre1 ); + mce1 = em.merge( mce1 ); + em.getTransaction().commit(); + + mcre1Id = mcre1.getId(); + + // No changes. + em.getTransaction().begin(); + mce1 = em.find( MultipleCollectionEntity.class, mce1.getId() ); + mce1 = em.merge( mce1 ); + em.getTransaction().commit(); + + em.close(); + em = getEntityManager(); + + // Revision 3 - updating detached collection. + em.getTransaction().begin(); + mce1.removeRefEntity1( mcre1 ); + mce1 = em.merge( mce1 ); + em.getTransaction().commit(); + + em.close(); + em = getEntityManager(); + + // Revision 4 - updating detached entity, no changes to collection attributes. + em.getTransaction().begin(); + mce1.setRefEntities1( new ArrayList() ); + mce1.setRefEntities2( new ArrayList() ); + mce1.setText( "MultipleCollectionEntity-1-2" ); + mce1 = em.merge( mce1 ); + em.getTransaction().commit(); + + em.close(); + em = getEntityManager(); + + // No changes to detached entity (collections were empty before). + em.getTransaction().begin(); + mce1.setRefEntities1( new ArrayList() ); + mce1.setRefEntities2( new ArrayList() ); + mce1 = em.merge( mce1 ); + em.getTransaction().commit(); + + // Revision 5 - addition. + em.getTransaction().begin(); + MultipleCollectionEntity mce2 = new MultipleCollectionEntity(); + mce2.setText( "MultipleCollectionEntity-2-1" ); + MultipleCollectionRefEntity2 mcre2 = new MultipleCollectionRefEntity2(); + mcre2.setText( "MultipleCollectionRefEntity2-1-1" ); + mcre2.setMultipleCollectionEntity( mce2 ); + mce2.addRefEntity2( mcre2 ); + em.persist( mce2 ); // Cascade persisting related entity. + em.getTransaction().commit(); + + mce2Id = mce2.getId(); + mcre2Id = mcre2.getId(); + + em.close(); + } + + @Test + public void testHasChanged() { + List list = queryForPropertyHasChanged( MultipleCollectionEntity.class, mce1Id, "text" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 4 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( MultipleCollectionEntity.class, mce1Id, "refEntities1" ); + assertEquals( 3, list.size() ); + assertEquals( makeList( 1, 2, 3 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( MultipleCollectionEntity.class, mce1Id, "refEntities2" ); + assertEquals( 1, list.size() ); + assertEquals( makeList( 1 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( MultipleCollectionRefEntity1.class, mcre1Id, "text" ); + assertEquals( 1, list.size() ); + assertEquals( makeList( 2 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( MultipleCollectionEntity.class, mce2Id, "text" ); + assertEquals( 1, list.size() ); + assertEquals( makeList( 5 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( MultipleCollectionEntity.class, mce2Id, "refEntities2" ); + assertEquals( 1, list.size() ); + assertEquals( makeList( 5 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( MultipleCollectionRefEntity2.class, mcre2Id, "text" ); + assertEquals( 1, list.size() ); + assertEquals( makeList( 5 ), extractRevisionNumbers( list ) ); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedManualFlush.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedManualFlush.java new file mode 100644 index 0000000000..b6d2630cad --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedManualFlush.java @@ -0,0 +1,65 @@ +package org.hibernate.envers.test.integration.modifiedflags; + +import java.util.List; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.integration.basic.BasicTestEntity1; +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.envers.test.tools.TestTools.extractRevisionNumbers; +import static org.hibernate.envers.test.tools.TestTools.makeList; +import static org.junit.Assert.assertEquals; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-7918") +public class HasChangedManualFlush extends AbstractModifiedFlagsEntityTest { + private Integer id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { BasicTestEntity1.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 + em.getTransaction().begin(); + BasicTestEntity1 entity = new BasicTestEntity1( "str1", 1 ); + em.persist( entity ); + em.getTransaction().commit(); + + id = entity.getId(); + + // Revision 2 - both properties (str1 and long1) should be marked as modified. + em.getTransaction().begin(); + entity = em.find( BasicTestEntity1.class, entity.getId() ); + entity.setStr1( "str2" ); + entity = em.merge( entity ); + em.flush(); + entity.setLong1( 2 ); + entity = em.merge( entity ); + em.flush(); + em.getTransaction().commit(); + + em.close(); + } + + @Test + public void testHasChangedOnDoubleFlush() { + List list = queryForPropertyHasChanged( BasicTestEntity1.class, id, "str1" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 2 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( BasicTestEntity1.class, id, "long1" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 2 ), extractRevisionNumbers( list ) ); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedMergeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedMergeTest.java new file mode 100644 index 0000000000..6971ce9da4 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedMergeTest.java @@ -0,0 +1,107 @@ +package org.hibernate.envers.test.integration.modifiedflags; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.onetomany.ListRefEdEntity; +import org.hibernate.envers.test.entities.onetomany.ListRefIngEntity; +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.envers.test.tools.TestTools.extractRevisionNumbers; +import static org.hibernate.envers.test.tools.TestTools.makeList; +import static org.junit.Assert.assertEquals; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class HasChangedMergeTest extends AbstractModifiedFlagsEntityTest { + private Integer parent1Id = null; + private Integer child1Id = null; + + private Integer parent2Id = null; + private Integer child2Id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { ListRefEdEntity.class, ListRefIngEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 - data preparation + em.getTransaction().begin(); + ListRefEdEntity parent1 = new ListRefEdEntity( 1, "initial data" ); + parent1.setReffering( new ArrayList() ); // Empty collection is not the same as null reference. + ListRefEdEntity parent2 = new ListRefEdEntity( 2, "initial data" ); + parent2.setReffering( new ArrayList() ); + em.persist( parent1 ); + em.persist( parent2 ); + em.getTransaction().commit(); + + // Revision 2 - inserting new child entity and updating parent + em.getTransaction().begin(); + parent1 = em.find( ListRefEdEntity.class, parent1.getId() ); + ListRefIngEntity child1 = new ListRefIngEntity( 1, "initial data", parent1 ); + em.persist( child1 ); + parent1.setData( "updated data" ); + parent1 = em.merge( parent1 ); + em.getTransaction().commit(); + + // Revision 3 - updating parent, flushing and adding new child + em.getTransaction().begin(); + parent2 = em.find( ListRefEdEntity.class, parent2.getId() ); + parent2.setData( "updated data" ); + parent2 = em.merge( parent2 ); + em.flush(); + ListRefIngEntity child2 = new ListRefIngEntity( 2, "initial data", parent2 ); + em.persist( child2 ); + em.getTransaction().commit(); + + parent1Id = parent1.getId(); + child1Id = child1.getId(); + + parent2Id = parent2.getId(); + child2Id = child2.getId(); + + em.close(); + } + + @Test + @TestForIssue(jiraKey = "HHH-7948") + public void testOneToManyInsertChildUpdateParent() { + List list = queryForPropertyHasChanged( ListRefEdEntity.class, parent1Id, "data" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 2 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( ListRefEdEntity.class, parent1Id, "reffering" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 2 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( ListRefIngEntity.class, child1Id, "reference" ); + assertEquals( 1, list.size() ); + assertEquals( makeList( 2 ), extractRevisionNumbers( list ) ); + } + + @Test + @TestForIssue(jiraKey = "HHH-7948") + public void testOneToManyUpdateParentInsertChild() { + List list = queryForPropertyHasChanged( ListRefEdEntity.class, parent2Id, "data" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 3 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( ListRefEdEntity.class, parent2Id, "reffering" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 3 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( ListRefIngEntity.class, child2Id, "reference" ); + assertEquals( 1, list.size() ); + assertEquals( makeList( 3 ), extractRevisionNumbers( list ) ); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AggregateQuery.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AggregateQuery.java index 3e40c76b16..5f04155a06 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AggregateQuery.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AggregateQuery.java @@ -23,63 +23,63 @@ */ package org.hibernate.envers.test.integration.query; +import java.util.Arrays; +import java.util.List; import javax.persistence.EntityManager; +import org.junit.Assert; import org.junit.Test; import org.hibernate.envers.query.AuditEntity; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; import org.hibernate.envers.test.entities.IntTestEntity; +import org.hibernate.envers.test.entities.ids.UnusualIdNamingEntity; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.testing.TestForIssue; + /** * @author Adam Warski (adam at warski dot org) */ @SuppressWarnings({"unchecked"}) public class AggregateQuery extends BaseEnversJPAFunctionalTestCase { - @Override protected Class[] getAnnotatedClasses() { - return new Class[] { IntTestEntity.class }; + return new Class[] { IntTestEntity.class, UnusualIdNamingEntity.class }; } @Test @Priority(10) public void initData() { - // Revision 1 EntityManager em = getEntityManager(); - em.getTransaction().begin(); + // Revision 1 + em.getTransaction().begin(); IntTestEntity ite1 = new IntTestEntity(2); IntTestEntity ite2 = new IntTestEntity(10); - em.persist(ite1); em.persist(ite2); - Integer id1 = ite1.getId(); Integer id2 = ite2.getId(); - em.getTransaction().commit(); // Revision 2 em.getTransaction().begin(); - IntTestEntity ite3 = new IntTestEntity(8); + UnusualIdNamingEntity uine1 = new UnusualIdNamingEntity( "id1", "data1" ); + em.persist( uine1 ); em.persist(ite3); - ite1 = em.find(IntTestEntity.class, id1); - ite1.setNumber(0); - em.getTransaction().commit(); // Revision 3 em.getTransaction().begin(); - ite2 = em.find(IntTestEntity.class, id2); - ite2.setNumber(52); - em.getTransaction().commit(); + + em.close(); } @Test @@ -111,4 +111,69 @@ public class AggregateQuery extends BaseEnversJPAFunctionalTestCase { assert (Integer) ver3[0] == 52; assert (Double) ver3[1] == 20.0; } + + @Test + @TestForIssue( jiraKey = "HHH-8036" ) + public void testEntityIdProjection() { + Integer maxId = (Integer) getAuditReader().createQuery().forRevisionsOfEntity( IntTestEntity.class, true, true ) + .addProjection( AuditEntity.id().max() ) + .add( AuditEntity.revisionNumber().gt( 2 ) ) + .getSingleResult(); + Assert.assertEquals( Integer.valueOf( 2 ), maxId ); + } + + @Test + @TestForIssue( jiraKey = "HHH-8036" ) + public void testEntityIdRestriction() { + List list = getAuditReader().createQuery().forRevisionsOfEntity( IntTestEntity.class, true, true ) + .add( AuditEntity.id().between( 2, 3 ) ) + .getResultList(); + Assert.assertTrue( + TestTools.checkList( list, + new IntTestEntity( 10, 2 ), new IntTestEntity( 8, 3 ), new IntTestEntity( 52, 2 ) + ) + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-8036" ) + public void testEntityIdOrdering() { + List list = getAuditReader().createQuery().forRevisionsOfEntity( IntTestEntity.class, true, true ) + .add( AuditEntity.revisionNumber().lt( 2 ) ) + .addOrder( AuditEntity.id().desc() ) + .getResultList(); + Assert.assertEquals( Arrays.asList( new IntTestEntity( 10, 2 ), new IntTestEntity( 2, 1 ) ), list ); + } + + @Test + @TestForIssue( jiraKey = "HHH-8036" ) + public void testUnusualIdFieldName() { + UnusualIdNamingEntity entity = (UnusualIdNamingEntity) getAuditReader().createQuery() + .forRevisionsOfEntity( UnusualIdNamingEntity.class, true, true ) + .add( AuditEntity.id().like( "id1" ) ) + .getSingleResult(); + Assert.assertEquals( new UnusualIdNamingEntity( "id1", "data1" ), entity ); + } + + @Test + @TestForIssue( jiraKey = "HHH-8036" ) + public void testEntityIdModifiedFlagNotSupported() { + try { + getAuditReader().createQuery().forRevisionsOfEntity( IntTestEntity.class, true, true ) + .add( AuditEntity.id().hasChanged() ) + .getResultList(); + } + catch ( UnsupportedOperationException e1 ) { + try { + getAuditReader().createQuery().forRevisionsOfEntity( IntTestEntity.class, true, true ) + .add( AuditEntity.id().hasNotChanged() ) + .getResultList(); + } + catch ( UnsupportedOperationException e2 ) { + return; + } + Assert.fail(); + } + Assert.fail(); + } } \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/DeletedEntities.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/DeletedEntities.java index 6d9a94593c..ea1f3a36f3 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/DeletedEntities.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/DeletedEntities.java @@ -82,7 +82,7 @@ public class DeletedEntities extends BaseEnversJPAFunctionalTestCase { assert (Long) getAuditReader().createQuery().forEntitiesAtRevision(StrIntTestEntity.class, 1) .addProjection(AuditEntity.id().count("id")).getResultList().get(0) == 2; assert (Long) getAuditReader().createQuery().forEntitiesAtRevision(StrIntTestEntity.class, 2) - .addProjection(AuditEntity.id().count("id")).getResultList().get(0) == 1; + .addProjection(AuditEntity.id().count()).getResultList().get(0) == 1; } @Test diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/RevisionConstraintQuery.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/RevisionConstraintQuery.java index 9a22db5b50..c0870036fb 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/RevisionConstraintQuery.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/RevisionConstraintQuery.java @@ -159,10 +159,10 @@ public class RevisionConstraintQuery extends BaseEnversJPAFunctionalTestCase { .add(AuditEntity.id().eq(id1)) .getSingleResult(); - Assert.assertEquals(Integer.valueOf(4), (Integer) result[0]); - Assert.assertEquals(Long.valueOf(4), (Long) result[1]); + Assert.assertEquals(Integer.valueOf(4), result[0] ); + Assert.assertEquals(Long.valueOf(4), result[1] ); Assert.assertEquals(Long.valueOf(4), result[2]); - Assert.assertEquals(Integer.valueOf(1), (Integer) result[3]); + Assert.assertEquals(Integer.valueOf(1), result[3] ); } @Test @@ -186,7 +186,7 @@ public class RevisionConstraintQuery extends BaseEnversJPAFunctionalTestCase { .add(AuditEntity.id().eq(id1)) .getSingleResult(); - Assert.assertEquals(Long.valueOf(4), (Long) result); + Assert.assertEquals(Long.valueOf(4), result ); } @Test diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java index 9999914314..f88d44ec86 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java @@ -3,6 +3,7 @@ package org.hibernate.envers.test.performance; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.Assert; @@ -93,8 +94,8 @@ public class EvictAuditDataAfterCommitTest extends BaseEnversFunctionalTestCase private void checkEmptyAuditSessionCache(Session session, String ... auditEntityNames) { List entityNames = Arrays.asList(auditEntityNames); PersistenceContext persistenceContext = ((SessionImplementor) session).getPersistenceContext(); - for (Object entry : persistenceContext.getEntityEntries().values()) { - EntityEntry entityEntry = (EntityEntry) entry; + for ( Map.Entry entrySet : persistenceContext.reentrantSafeEntityEntries() ) { + final EntityEntry entityEntry = entrySet.getValue(); if (entityNames.contains(entityEntry.getEntityName())) { assert false : "Audit data shall not be stored in the session level cache. This causes performance issues."; } diff --git a/hibernate-envers/src/test/resources/mappings/oneToOne/bidirectional/eagerLoading.hbm.xml b/hibernate-envers/src/test/resources/mappings/oneToOne/bidirectional/eagerLoading.hbm.xml index 1a0847a95f..29d7083f3e 100644 --- a/hibernate-envers/src/test/resources/mappings/oneToOne/bidirectional/eagerLoading.hbm.xml +++ b/hibernate-envers/src/test/resources/mappings/oneToOne/bidirectional/eagerLoading.hbm.xml @@ -27,8 +27,8 @@ "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - - + + @@ -36,8 +36,8 @@ class="BidirectionalEagerHbmRefEdPK" column="BIDIRECTIONAL_REFERENCED_ID" /> - - + + diff --git a/hibernate-infinispan/hibernate-infinispan.gradle b/hibernate-infinispan/hibernate-infinispan.gradle index 386d2e44d3..a679910470 100644 --- a/hibernate-infinispan/hibernate-infinispan.gradle +++ b/hibernate-infinispan/hibernate-infinispan.gradle @@ -40,3 +40,10 @@ task sourcesTestJar(type: Jar, dependsOn:classes) { artifacts.archives packageTests artifacts.archives sourcesTestJar + +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM Infinispan' + instruction 'Bundle-SymbolicName', 'org.hibernate.infinispan' + } +} \ No newline at end of file diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java index d44417aecb..45b8fd99c3 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java @@ -39,10 +39,16 @@ import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.CacheException; import org.hibernate.cache.infinispan.collection.CollectionRegionImpl; import org.hibernate.cache.infinispan.entity.EntityRegionImpl; +import org.hibernate.cache.infinispan.impl.BaseRegion; +import org.hibernate.cache.infinispan.naturalid.NaturalIdRegionImpl; import org.hibernate.cache.infinispan.query.QueryResultsRegionImpl; +import org.hibernate.cache.infinispan.timestamp.ClusteredTimestampsRegionImpl; import org.hibernate.cache.infinispan.timestamp.TimestampTypeOverrides; import org.hibernate.cache.infinispan.timestamp.TimestampsRegionImpl; import org.hibernate.cache.infinispan.tm.HibernateTransactionManagerLookup; +import org.hibernate.cache.infinispan.util.CacheCommandFactory; +import org.hibernate.cache.infinispan.util.Caches; +import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.spi.CollectionRegion; import org.hibernate.cache.spi.EntityRegion; import org.hibernate.cache.spi.NaturalIdRegion; @@ -55,6 +61,24 @@ import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.ServiceRegistry; import org.hibernate.engine.config.spi.StandardConverters; +import org.hibernate.internal.util.ClassLoaderHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.infinispan.AdvancedCache; +import org.infinispan.commands.module.ModuleCommandFactory; +import org.infinispan.configuration.cache.CacheMode; +import org.infinispan.configuration.cache.Configuration; +import org.infinispan.configuration.cache.ConfigurationBuilder; +import org.infinispan.configuration.parsing.ConfigurationBuilderHolder; +import org.infinispan.configuration.parsing.ParserRegistry; +import org.infinispan.factories.GlobalComponentRegistry; +import org.infinispan.manager.DefaultCacheManager; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.transaction.TransactionMode; +import org.infinispan.transaction.lookup.GenericTransactionManagerLookup; +import org.infinispan.util.FileLookupFactory; +import org.infinispan.util.concurrent.IsolationLevel; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; /** * A {@link RegionFactory} for Infinispan-backed cache @@ -383,10 +407,10 @@ public class InfinispanRegionFactory extends AbstractRegionFactory { try { String configLoc = ConfigurationHelper.getString( INFINISPAN_CONFIG_RESOURCE_PROP, properties, DEF_INFINISPAN_CONFIG_RESOURCE); - ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = ClassLoaderHelper.getContextClassLoader(); InputStream is = FileLookupFactory.newInstance().lookupFileStrict( - configLoc, ctxClassLoader); - ParserRegistry parserRegistry = new ParserRegistry(ctxClassLoader); + configLoc, classLoader); + ParserRegistry parserRegistry = new ParserRegistry(classLoader); ConfigurationBuilderHolder holder = parserRegistry.parse(is); // Override global jmx statistics exposure diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/JndiInfinispanRegionFactory.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/JndiInfinispanRegionFactory.java index 068e4dea1a..b7f6afa696 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/JndiInfinispanRegionFactory.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/JndiInfinispanRegionFactory.java @@ -28,6 +28,18 @@ import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jndi.spi.JndiService; import org.infinispan.manager.EmbeddedCacheManager; +import java.util.Properties; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.hibernate.cache.CacheException; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.internal.util.jndi.JndiHelper; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; /** * A {@link org.hibernate.cache.spi.RegionFactory} for Infinispan-backed cache diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java index 9f07d706f4..003dce0b16 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java @@ -194,7 +194,7 @@ public class PutFromLoadValidator { // or regionRemoved has been called. Check if we can proceed if (now > invalidationTimestamp) { Long removedTime = recentRemovals.get(key); - if (removedTime == null || now > removedTime.longValue()) { + if (removedTime == null || now > removedTime ) { // It's legal to proceed. But we have to record this key // in pendingPuts so releasePutFromLoadLock can find it. // To do this we basically simulate a normal "register @@ -280,7 +280,7 @@ public class PutFromLoadValidator { // Don't let recentRemovals map become a memory leak RecentRemoval toClean = null; - boolean attemptClean = removal.timestamp.longValue() > earliestRemovalTimestamp; + boolean attemptClean = removal.timestamp > earliestRemovalTimestamp; removalsLock.lock(); try { removalsQueue.add(removal); @@ -290,7 +290,7 @@ public class PutFromLoadValidator { // just added it toClean = removalsQueue.remove(0); } - earliestRemovalTimestamp = removalsQueue.get(0).timestamp.longValue(); + earliestRemovalTimestamp = removalsQueue.get( 0 ).timestamp; } } finally { removalsLock.unlock(); @@ -526,7 +526,7 @@ public class PutFromLoadValidator { private RecentRemoval(Object key, long nakedPutInvalidationPeriod) { this.key = key; - timestamp = Long.valueOf(System.currentTimeMillis() + nakedPutInvalidationPeriod); + timestamp = System.currentTimeMillis() + nakedPutInvalidationPeriod; } } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java index ad66557cfb..7b61bca2b8 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java @@ -8,6 +8,7 @@ import org.hibernate.cache.spi.EntityRegion; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; + import org.infinispan.AdvancedCache; /** @@ -17,22 +18,25 @@ import org.infinispan.AdvancedCache; */ public class EntityRegionImpl extends BaseTransactionalDataRegion implements EntityRegion { - public EntityRegionImpl(AdvancedCache cache, String name, - CacheDataDescription metadata, RegionFactory factory) { - super(cache, name, metadata, factory); - } + public EntityRegionImpl(AdvancedCache cache, String name, + CacheDataDescription metadata, RegionFactory factory) { + super( cache, name, metadata, factory ); + } - public EntityRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException { - if (AccessType.READ_ONLY.equals(accessType)) { - return new ReadOnlyAccess(this); - } else if (AccessType.TRANSACTIONAL.equals(accessType)) { - return new TransactionalAccess(this); - } - throw new CacheException("Unsupported access type [" + accessType.getExternalName() + "]"); - } + @Override + public EntityRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException { + switch ( accessType ) { + case READ_ONLY: + return new ReadOnlyAccess( this ); + case TRANSACTIONAL: + return new TransactionalAccess( this ); + default: + throw new CacheException( "Unsupported access type [" + accessType.getExternalName() + "]" ); + } + } - public PutFromLoadValidator getPutFromLoadValidator() { - return new PutFromLoadValidator(cache); - } + public PutFromLoadValidator getPutFromLoadValidator() { + return new PutFromLoadValidator( cache ); + } } \ No newline at end of file diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java index 880d3eece7..8c57ad512c 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java @@ -23,6 +23,7 @@ public abstract class BaseTransactionalDataRegion this.metadata = metadata; } + @Override public CacheDataDescription getCacheDataDescription() { return metadata; } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java index 8f6f43fdb9..04aa44ae60 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java @@ -26,12 +26,14 @@ public class NaturalIdRegionImpl extends BaseTransactionalDataRegion @Override public NaturalIdRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException { - if (AccessType.READ_ONLY.equals(accessType)) { - return new ReadOnlyAccess(this); - } else if (AccessType.TRANSACTIONAL.equals(accessType)) { - return new TransactionalAccess(this); + switch ( accessType ){ + case READ_ONLY: + return new ReadOnlyAccess( this ); + case TRANSACTIONAL: + return new TransactionalAccess( this ); + default: + throw new CacheException( "Unsupported access type [" + accessType.getExternalName() + "]" ); } - throw new CacheException("Unsupported access type [" + accessType.getExternalName() + "]"); } public PutFromLoadValidator getPutFromLoadValidator() { diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/ReadOnlyAccess.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/ReadOnlyAccess.java index f2ca29e713..0c173d0a5e 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/ReadOnlyAccess.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/ReadOnlyAccess.java @@ -10,7 +10,6 @@ import org.hibernate.cache.spi.access.SoftLock; * @author Strong Liu */ class ReadOnlyAccess extends TransactionalAccess { - private static final Log log = LogFactory.getLog( ReadOnlyAccess.class ); ReadOnlyAccess(NaturalIdRegionImpl naturalIdRegion) { super( naturalIdRegion ); diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/tm/HibernateTransactionManagerLookup.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/tm/HibernateTransactionManagerLookup.java index fb09623f13..0be5b2c415 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/tm/HibernateTransactionManagerLookup.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/tm/HibernateTransactionManagerLookup.java @@ -38,12 +38,7 @@ public class HibernateTransactionManagerLookup implements org.infinispan.transac private final JtaPlatform jtaPlatform; public HibernateTransactionManagerLookup(Settings settings, Properties properties) { - if ( settings != null ) { - jtaPlatform = settings.getJtaPlatform(); - } - else { - jtaPlatform = null; - } + this.jtaPlatform = settings != null ? settings.getJtaPlatform() : null; } public HibernateTransactionManagerLookup(ServiceRegistry serviceRegistry) { @@ -55,6 +50,7 @@ public class HibernateTransactionManagerLookup implements org.infinispan.transac } } + @Override public TransactionManager getTransactionManager() throws Exception { return jtaPlatform == null ? null : jtaPlatform.retrieveTransactionManager(); } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractGeneralDataRegionTestCase.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractGeneralDataRegionTestCase.java index 4817fa983b..1459e66d00 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractGeneralDataRegionTestCase.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractGeneralDataRegionTestCase.java @@ -82,60 +82,52 @@ public abstract class AbstractGeneralDataRegionTestCase extends AbstractRegionIm private void evictOrRemoveTest() throws Exception { Configuration cfg = createConfiguration(); - InfinispanRegionFactory regionFactory = null; - InfinispanRegionFactory remoteRegionFactory = null; - try { - regionFactory = CacheTestUtil.startRegionFactory( - new StandardServiceRegistryBuilder().applySettings( cfg.getProperties() ).build(), - cfg, - getCacheTestSupport() - ); - boolean invalidation = false; + InfinispanRegionFactory regionFactory = CacheTestUtil.startRegionFactory( + new StandardServiceRegistryBuilder().applySettings( cfg.getProperties() ).build(), + cfg, + getCacheTestSupport() + ); + boolean invalidation = false; - // Sleep a bit to avoid concurrent FLUSH problem - avoidConcurrentFlush(); + // Sleep a bit to avoid concurrent FLUSH problem + avoidConcurrentFlush(); - GeneralDataRegion localRegion = (GeneralDataRegion) createRegion( - regionFactory, - getStandardRegionName( REGION_PREFIX ), cfg.getProperties(), null - ); + GeneralDataRegion localRegion = (GeneralDataRegion) createRegion( + regionFactory, + getStandardRegionName( REGION_PREFIX ), cfg.getProperties(), null + ); - cfg = createConfiguration(); - remoteRegionFactory = CacheTestUtil.startRegionFactory( - new StandardServiceRegistryBuilder().applySettings( cfg.getProperties() ).build(), - cfg, - getCacheTestSupport() - ); + cfg = createConfiguration(); + regionFactory = CacheTestUtil.startRegionFactory( + new StandardServiceRegistryBuilder().applySettings( cfg.getProperties() ).build(), + cfg, + getCacheTestSupport() + ); - GeneralDataRegion remoteRegion = (GeneralDataRegion) createRegion( - remoteRegionFactory, - getStandardRegionName( REGION_PREFIX ), - cfg.getProperties(), - null - ); + GeneralDataRegion remoteRegion = (GeneralDataRegion) createRegion( + regionFactory, + getStandardRegionName( REGION_PREFIX ), + cfg.getProperties(), + null + ); - assertNull( "local is clean", localRegion.get( KEY ) ); - assertNull( "remote is clean", remoteRegion.get( KEY ) ); + assertNull( "local is clean", localRegion.get( KEY ) ); + assertNull( "remote is clean", remoteRegion.get( KEY ) ); - regionPut( localRegion ); - assertEquals( VALUE1, localRegion.get( KEY ) ); + regionPut(localRegion); + assertEquals( VALUE1, localRegion.get( KEY ) ); - // allow async propagation - sleep( 250 ); - Object expected = invalidation ? null : VALUE1; - assertEquals( expected, remoteRegion.get( KEY ) ); + // allow async propagation + sleep( 250 ); + Object expected = invalidation ? null : VALUE1; + assertEquals( expected, remoteRegion.get( KEY ) ); - regionEvict( localRegion ); + regionEvict(localRegion); - // allow async propagation - sleep( 250 ); - assertEquals( null, localRegion.get( KEY ) ); - assertEquals( null, remoteRegion.get( KEY ) ); - } - finally { - CacheTestUtil.stopRegionFactory( regionFactory, getCacheTestSupport() ); - CacheTestUtil.stopRegionFactory( remoteRegionFactory, getCacheTestSupport() ); - } + // allow async propagation + sleep( 250 ); + assertEquals( null, localRegion.get( KEY ) ); + assertEquals( null, remoteRegion.get( KEY ) ); } protected void regionEvict(GeneralDataRegion region) throws Exception { diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java index c4cee6f9f3..46a4f2553a 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java @@ -364,7 +364,7 @@ public class EntityCollectionInvalidationTestCase extends DualNodeTestCase { if ( !event.isPre() ) { CacheKey cacheKey = (CacheKey) event.getKey(); Integer primKey = (Integer) cacheKey.getKey(); - String key = (String) cacheKey.getEntityOrRoleName() + '#' + primKey; + String key = cacheKey.getEntityOrRoleName() + '#' + primKey; log.debug( "MyListener[" + name + "] - Visiting key " + key ); // String name = fqn.toString(); String token = ".functional."; diff --git a/hibernate-osgi/hibernate-osgi.gradle b/hibernate-osgi/hibernate-osgi.gradle new file mode 100644 index 0000000000..a9c0ce376d --- /dev/null +++ b/hibernate-osgi/hibernate-osgi.gradle @@ -0,0 +1,13 @@ +dependencies { + compile( project( ':hibernate-core' ) ) + compile( project( ':hibernate-entitymanager' ) ) + compile( "org.osgi:org.osgi.core:4.2.0" ) +} + +jar { + manifest { + instruction 'Bundle-Activator', 'org.hibernate.osgi.HibernateBundleActivator' + instruction 'Bundle-Description', 'Hibernate ORM OSGi' + instruction 'Bundle-SymbolicName', 'org.hibernate.osgi' + } +} diff --git a/hibernate-osgi/src/main/java/org/hibernate/osgi/CachedBundle.java b/hibernate-osgi/src/main/java/org/hibernate/osgi/CachedBundle.java new file mode 100644 index 0000000000..2cc663d672 --- /dev/null +++ b/hibernate-osgi/src/main/java/org/hibernate/osgi/CachedBundle.java @@ -0,0 +1,94 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.osgi; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.osgi.framework.Bundle; + +/** + * Integrates a Bundle, its key, and classes/resources that have been found + * through its ClassLoader. Primarily used to clear the OsgiClassLoader + * caches once the Bundle is deactivated. + * + * @author Brett Meyer + */ +public class CachedBundle { + + private Bundle bundle; + + private String key; + + private List classNames = new ArrayList(); + + private List resourceNames = new ArrayList(); + + private List resourceListNames = new ArrayList(); + + public CachedBundle( Bundle bundle, String key ) { + this.bundle = bundle; + this.key = key; + } + + public Class loadClass(String name) throws ClassNotFoundException { + Class clazz = bundle.loadClass( name ); + if ( clazz != null ) { + classNames.add( name ); + } + return clazz; + } + + public URL getResource(String name) { + URL resource = bundle.getResource( name ); + if ( resource != null ) { + resourceNames.add( name ); + } + return resource; + } + + public Enumeration getResources(String name) throws IOException { + Enumeration resourceList = bundle.getResources( name ); + if ( resourceList != null ) { + resourceListNames.add( name ); + } + return resourceList; + } + + public String getKey() { + return key; + } + + public List getClassNames() { + return classNames; + } + + public List getResourceNames() { + return resourceNames; + } + + public List getResourceListNames() { + return resourceListNames; + } +} diff --git a/hibernate-osgi/src/main/java/org/hibernate/osgi/HibernateBundleActivator.java b/hibernate-osgi/src/main/java/org/hibernate/osgi/HibernateBundleActivator.java new file mode 100644 index 0000000000..22b64c492b --- /dev/null +++ b/hibernate-osgi/src/main/java/org/hibernate/osgi/HibernateBundleActivator.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.osgi; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.persistence.spi.PersistenceProvider; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.util.ClassLoaderHelper; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleListener; + +/** + * @author Brett Meyer + * @author Martin Neimeier + */ +public class HibernateBundleActivator + implements BundleActivator, /*ServiceListener,*/ BundleListener { + + private OsgiClassLoader osgiClassLoader; + + @Override + public void start(BundleContext context) throws Exception { + + // register this instance as a bundle listener to get informed about all + // bundle live cycle events + context.addBundleListener(this); + + osgiClassLoader = new OsgiClassLoader(); + + ClassLoaderHelper.overridenClassLoader = osgiClassLoader; + + for ( Bundle bundle : context.getBundles() ) { + handleBundleChange( bundle ); + } + + HibernatePersistenceProvider hpp = new HibernatePersistenceProvider(); + Map map = new HashMap(); + map.put( AvailableSettings.JTA_PLATFORM, new OsgiJtaPlatform( context ) ); + hpp.setEnvironmentProperties( map ); + + Properties properties = new Properties(); + properties.put( "javax.persistence.provider", HibernatePersistenceProvider.class.getName() ); + context.registerService( PersistenceProvider.class.getName(), hpp, properties ); + } + + @Override + public void stop(BundleContext context) throws Exception { + context.removeBundleListener(this); + + // Nothing else to do. When re-activated, this Activator will be + // re-started and the EMF re-created. + } + + @Override + public void bundleChanged(BundleEvent event) { + handleBundleChange( event.getBundle() ); + + } + + private void handleBundleChange( Bundle bundle ) { + if ( bundle.getState() == Bundle.ACTIVE ) { + osgiClassLoader.registerBundle(bundle); + } else { + osgiClassLoader.unregisterBundle(bundle); + } + } +} diff --git a/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiClassLoader.java b/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiClassLoader.java new file mode 100644 index 0000000000..47ace9479b --- /dev/null +++ b/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiClassLoader.java @@ -0,0 +1,178 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.osgi; + +import java.net.URL; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.osgi.framework.Bundle; + +/** + * Custom OSGI ClassLoader helper which knows all the "interesting" bundles and + * encapsulates the OSGi related capabilities. + * + * @author Brett Meyer + */ +public class OsgiClassLoader extends ClassLoader { + + private Map bundles = new HashMap(); + + private Map> classCache = new HashMap>(); + + private Map resourceCache = new HashMap(); + + private Map> resourceListCache = new HashMap>(); + + /** + * Load the class and break on first found match. + * TODO: Should this throw a different exception or warn if multiple + * classes were found? Naming collisions can and do happen in OSGi... + */ + @SuppressWarnings("rawtypes") + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if ( classCache.containsKey( name ) ) { + return classCache.get( name ); + } + + for ( CachedBundle bundle : bundles.values() ) { + try { + Class clazz = bundle.loadClass( name ); + if ( clazz != null ) { + classCache.put( name, clazz ); + return clazz; + } + } + catch ( Exception ignore ) { + } + } + + throw new ClassNotFoundException( "Could not load requested class : " + name ); + } + + /** + * Load the class and break on first found match. + * TODO: Should this throw a different exception or warn if multiple + * classes were found? Naming collisions can and do happen in OSGi... + */ + @Override + protected URL findResource(String name) { + if ( resourceCache.containsKey( name ) ) { + return resourceCache.get( name ); + } + + for ( CachedBundle bundle : bundles.values() ) { + try { + URL resource = bundle.getResource( name ); + if ( resource != null ) { + resourceCache.put( name, resource ); + return resource; + } + } + catch ( Exception ignore ) { + } + } + // TODO: Error? + return null; + } + + /** + * Load the class and break on first found match. + * TODO: Should this throw a different exception or warn if multiple + * classes were found? Naming collisions can and do happen in OSGi... + */ + @SuppressWarnings("unchecked") + @Override + protected Enumeration findResources(String name) { + if ( resourceListCache.containsKey( name ) ) { + return resourceListCache.get( name ); + } + + for ( CachedBundle bundle : bundles.values() ) { + try { + Enumeration resources = bundle.getResources( name ); + if ( resources != null ) { + resourceListCache.put( name, resources ); + return resources; + } + } + catch ( Exception ignore ) { + } + } + // TODO: Error? + return null; + } + + /** + * Register the bundle with this class loader + */ + public void registerBundle(Bundle bundle) { + if ( bundle != null ) { + synchronized ( bundles ) { + String key = getBundleKey( bundle ); + if ( !bundles.containsKey( key ) ) { + bundles.put( key, new CachedBundle( bundle, key ) ); + } + } + } + } + + /** + * Unregister the bundle from this class loader + */ + public void unregisterBundle(Bundle bundle) { + if ( bundle != null ) { + synchronized ( bundles ) { + String key = getBundleKey( bundle ); + if ( bundles.containsKey( key ) ) { + CachedBundle cachedBundle = bundles.remove( key ); + clearCache( classCache, cachedBundle.getClassNames() ); + clearCache( resourceCache, cachedBundle.getResourceNames() ); + clearCache( resourceListCache, cachedBundle.getResourceListNames() ); + } + } + } + } + + private void clearCache( Map cache, List names ) { + for ( String name : names ) { + cache.remove( name ); + } + } + + public void clear() { + bundles.clear(); + classCache.clear(); + resourceCache.clear(); + resourceListCache.clear(); + } + + protected static String getBundleKey(Bundle bundle) { + return bundle.getSymbolicName() + " " + bundle.getVersion().toString(); + } + +} diff --git a/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiJtaPlatform.java b/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiJtaPlatform.java new file mode 100644 index 0000000000..29ae4707d4 --- /dev/null +++ b/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiJtaPlatform.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * JBoss, Home of Professional Open Source + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License, v. 2.1. + * This program is distributed in the hope that it will be useful, but WITHOUT A + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License, + * v.2.1 along with this distribution; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.hibernate.osgi; + +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * Offers the JTA Platform provided by the OSGi container. The Enterprise + * OSGi spec requires all containers to register UserTransaction + * and TransactionManager OSGi services. + * + * @author Brett Meyer + */ +public class OsgiJtaPlatform implements JtaPlatform { + + private static final long serialVersionUID = 1L; + + private BundleContext bundleContext; + + public OsgiJtaPlatform(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Override + public TransactionManager retrieveTransactionManager() { + ServiceReference sr = bundleContext.getServiceReference( TransactionManager.class.getName() ); + return (TransactionManager) bundleContext.getService( sr ); + } + + @Override + public UserTransaction retrieveUserTransaction() { + ServiceReference sr = bundleContext.getServiceReference( UserTransaction.class.getName() ); + return (UserTransaction) bundleContext.getService( sr ); + } + + @Override + public Object getTransactionIdentifier(Transaction transaction) { + // AbstractJtaPlatform just uses the transaction itself. + return transaction; + } + + @Override + public boolean canRegisterSynchronization() { + // TODO + return false; + } + + @Override + public void registerSynchronization(Synchronization synchronization) { + // TODO + } + + @Override + public int getCurrentStatus() throws SystemException { + return retrieveTransactionManager().getStatus(); + } + +} diff --git a/hibernate-proxool/hibernate-proxool.gradle b/hibernate-proxool/hibernate-proxool.gradle index f57746b053..c6b0bcb133 100644 --- a/hibernate-proxool/hibernate-proxool.gradle +++ b/hibernate-proxool/hibernate-proxool.gradle @@ -1,6 +1,12 @@ - dependencies { compile project( ':hibernate-core' ) compile( libraries.proxool ) testCompile project( ':hibernate-testing' ) } + +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM Proxool' + instruction 'Bundle-SymbolicName', 'org.hibernate.proxool' + } +} \ No newline at end of file diff --git a/hibernate-testing/hibernate-testing.gradle b/hibernate-testing/hibernate-testing.gradle index efb8bf39ee..cb1da935d8 100644 --- a/hibernate-testing/hibernate-testing.gradle +++ b/hibernate-testing/hibernate-testing.gradle @@ -10,4 +10,11 @@ dependencies { compile ( libraries.jboss_jta ) { transitive=false; } +} + +jar { + manifest { + instruction 'Bundle-Description', 'Hibernate ORM Testing' + instruction 'Bundle-SymbolicName', 'org.hibernate.testing' + } } \ No newline at end of file diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/RequiresDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/RequiresDialect.java index 3371491ee9..2217068eff 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/RequiresDialect.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/RequiresDialect.java @@ -34,6 +34,8 @@ import org.hibernate.dialect.Dialect; * Annotation used to indicate that a test should be run only when run against the * indicated dialects. * + * @see RequiresDialects + * * @author Hardy Ferentschik */ @Target({ ElementType.METHOD, ElementType.TYPE }) diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/RequiresDialects.java b/hibernate-testing/src/main/java/org/hibernate/testing/RequiresDialects.java new file mode 100644 index 0000000000..3affd4d0c4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/RequiresDialects.java @@ -0,0 +1,18 @@ +package org.hibernate.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Plural annotation for {@link RequiresDialect}. + * Useful when test needs to be run against more than one dialect because of a different reason. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface RequiresDialects { + RequiresDialect[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/SkipForDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/SkipForDialect.java index 2010706e6e..428185761e 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/SkipForDialect.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/SkipForDialect.java @@ -34,6 +34,8 @@ import org.hibernate.dialect.Dialect; * Annotation used to indicate that a test should be skipped when run against the * indicated dialects. * + * @see SkipForDialects + * * @author Hardy Ferentschik * @author Steve Ebersole */ diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/SkipForDialects.java b/hibernate-testing/src/main/java/org/hibernate/testing/SkipForDialects.java new file mode 100644 index 0000000000..5cc0d63162 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/SkipForDialects.java @@ -0,0 +1,18 @@ +package org.hibernate.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Plural annotation for {@link SkipForDialect}. + * Useful when more than one dialect needs to be skipped because of a different reason. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface SkipForDialects { + SkipForDialect[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseAnnotationBindingTestCase.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseAnnotationBindingTestCase.java index d75871f103..f58ce9af6f 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseAnnotationBindingTestCase.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseAnnotationBindingTestCase.java @@ -26,6 +26,9 @@ package org.hibernate.testing.junit4; import java.util.ArrayList; import java.util.List; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.metamodel.MetadataBuilder; +import org.hibernate.metamodel.MetadataSources; import org.junit.After; import org.junit.Rule; import org.junit.Test; @@ -33,9 +36,6 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.metamodel.MetadataBuilder; -import org.hibernate.metamodel.MetadataSources; import org.hibernate.metamodel.internal.MetadataImpl; import org.hibernate.metamodel.spi.binding.EntityBinding; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java index 6a504341f1..d005013be0 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java @@ -184,6 +184,25 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase { protected boolean isMetadataUsed() { return isMetadataUsed; } + protected void rebuildSessionFactory() { + if ( sessionFactory == null ) { + return; + } + buildSessionFactory(); + } + + + protected void afterConstructAndConfigureMetadata(MetadataImplementor metadataImplementor) { + + } + + private MetadataImplementor buildMetadata( + BootstrapServiceRegistry bootRegistry, + StandardServiceRegistryImpl serviceRegistry) { + MetadataSources sources = new MetadataSources( bootRegistry ); + addMappings( sources ); + return (MetadataImplementor) sources.getMetadataBuilder( serviceRegistry ).build(); + } protected void configMetadataBuilder(MetadataBuilder metadataBuilder, Configuration configuration) { //see if the naming strategy is the default one @@ -204,18 +223,6 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase { } } - protected void rebuildSessionFactory() { - if ( sessionFactory == null ) { - return; - } - buildSessionFactory(); - } - - - protected void afterConstructAndConfigureMetadata(MetadataImplementor metadataImplementor) { - - } - protected MetadataBuilder getMetadataBuilder( BootstrapServiceRegistry bootRegistry, StandardServiceRegistryImpl serviceRegistry) { diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/CustomRunner.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/CustomRunner.java index 6714f7e56b..105af9efe0 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/CustomRunner.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/CustomRunner.java @@ -30,7 +30,11 @@ import java.util.Comparator; import java.util.List; import org.jboss.logging.Logger; +import org.junit.BeforeClass; import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; @@ -44,8 +48,10 @@ import org.hibernate.testing.FailureExpected; import org.hibernate.testing.FailureExpectedWithNewMetamodel; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.RequiresDialects; import org.hibernate.testing.Skip; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.SkipForDialects; /** * The Hibernate-specific {@link org.junit.runner.Runner} implementation which layers {@link ExtendedFrameworkMethod} @@ -146,9 +152,22 @@ public class CustomRunner extends BlockJUnit4ClassRunner { ); } + /** + * {@inheritDoc} + * + * @see org.junit.runners.ParentRunner#classBlock(org.junit.runner.notification.RunNotifier) + */ + @Override + protected Statement classBlock( RunNotifier notifier ) { + log.info( BeforeClass.class.getSimpleName() + ": " + getName() ); + + return super.classBlock( notifier ); + } @Override protected Statement methodBlock(FrameworkMethod method) { + log.info( Test.class.getSimpleName() + ": " + method.getName() ); + final Statement originalMethodBlock = super.methodBlock( method ); final ExtendedFrameworkMethod extendedFrameworkMethod = (ExtendedFrameworkMethod) method; return new FailureExpectedHandler( this, originalMethodBlock, testClassMetadata, extendedFrameworkMethod, testInstance ); @@ -176,7 +195,9 @@ public class CustomRunner extends BlockJUnit4ClassRunner { } protected void sortMethods(List computedTestMethods) { - if( CollectionHelper.isEmpty( computedTestMethods ))return; + if ( CollectionHelper.isEmpty( computedTestMethods ) ) { + return; + } Collections.sort( computedTestMethods, new Comparator() { @Override public int compare(FrameworkMethod o1, FrameworkMethod o2) { @@ -257,9 +278,10 @@ public class CustomRunner extends BlockJUnit4ClassRunner { } } - // @SkipForDialect - SkipForDialect skipForDialectAnn = Helper.locateAnnotation( SkipForDialect.class, frameworkMethod, getTestClass() ); - if ( skipForDialectAnn != null ) { + // @SkipForDialects & @SkipForDialect + for ( SkipForDialect skipForDialectAnn : Helper.collectAnnotations( + SkipForDialect.class, SkipForDialects.class, frameworkMethod, getTestClass() + ) ) { for ( Class dialectClass : skipForDialectAnn.value() ) { if ( skipForDialectAnn.strictMatching() ) { if ( dialectClass.equals( dialect.getClass() ) ) { @@ -274,9 +296,10 @@ public class CustomRunner extends BlockJUnit4ClassRunner { } } - // @RequiresDialect - RequiresDialect requiresDialectAnn = Helper.locateAnnotation( RequiresDialect.class, frameworkMethod, getTestClass() ); - if ( requiresDialectAnn != null ) { + // @RequiresDialects & @RequiresDialect + for ( RequiresDialect requiresDialectAnn : Helper.collectAnnotations( + RequiresDialect.class, RequiresDialects.class, frameworkMethod, getTestClass() + ) ) { boolean foundMatch = false; for ( Class dialectClass : requiresDialectAnn.value() ) { foundMatch = requiresDialectAnn.strictMatching() diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java index a67206a018..3a9dbdd1b8 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java @@ -25,6 +25,9 @@ package org.hibernate.testing.junit4; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import org.junit.runners.model.FrameworkMethod; @@ -33,6 +36,8 @@ import org.junit.runners.model.TestClass; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.SkipForDialects; /** * Centralized utility functionality @@ -84,6 +89,38 @@ public class Helper { return annotation; } + /** + * @param singularAnnotationClass Singular annotation class (e.g. {@link SkipForDialect}). + * @param pluralAnnotationClass Plural annotation class (e.g. {@link SkipForDialects}). Assuming that the only + * declared method is an array of singular annotations. + * @param frameworkMethod Test method. + * @param testClass Test class. + * @param Singular annotation type. + * @param

Plural annotation type. + * @return Collection of all singular annotations or an empty list. + */ + @SuppressWarnings("unchecked") + public static List collectAnnotations(Class singularAnnotationClass, + Class

pluralAnnotationClass, + FrameworkMethod frameworkMethod, + TestClass testClass) { + final List collection = new LinkedList(); + final S singularAnn = Helper.locateAnnotation( singularAnnotationClass, frameworkMethod, testClass ); + if ( singularAnn != null ) { + collection.add( singularAnn ); + } + final P pluralAnn = Helper.locateAnnotation( pluralAnnotationClass, frameworkMethod, testClass ); + if ( pluralAnn != null ) { + try { + collection.addAll( Arrays.asList( (S[]) pluralAnnotationClass.getDeclaredMethods()[0].invoke(pluralAnn) ) ); + } + catch ( Exception e ) { + throw new RuntimeException( e ); + } + } + return collection; + } + public static String extractMessage(FailureExpected failureExpected) { StringBuilder buffer = new StringBuilder(); buffer.append( '(' ).append( failureExpected.jiraKey() ).append( ')' ); diff --git a/libraries.gradle b/libraries.gradle index 79c7296c1e..c34ee46d6e 100644 --- a/libraries.gradle +++ b/libraries.gradle @@ -30,7 +30,7 @@ ext { slf4jVersion = '1.6.1' junitVersion = '4.10' h2Version = '1.2.145' - bytemanVersion = '1.5.2' + bytemanVersion = '2.1.2' infinispanVersion = '5.2.0.Beta3' jnpVersion = '5.0.6.CR1' @@ -51,22 +51,16 @@ ext { dom4j: 'dom4j:dom4j:1.6.1@jar', // Javassist - javassist: 'org.javassist:javassist:3.17.1-GA', + javassist: 'org.javassist:javassist:3.15.0-GA', - // Connection pool - c3p0: "c3p0:c3p0:0.9.1", - proxool: "proxool:proxool:0.8.3", - - // Encache - ehcache: "net.sf.ehcache:ehcache-core:2.4.3", // Infinsipan infinispan: "org.infinispan:infinispan-core:${infinispanVersion}", // javax - jpa: 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Draft-7plus', + jpa: 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Draft-14', jta: 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:1.0.0.Final', validation: 'javax.validation:validation-api:1.0.0.GA', - jacc: 'org.jboss.spec.javax.security.jacc:jboss-jacc-api_1.4_spec:1.0.0.Final', + jacc: 'org.jboss.spec.javax.security.jacc:jboss-jacc-api_1.4_spec:1.0.2.Final', // logging logging: 'org.jboss.logging:jboss-logging:3.1.1.GA', @@ -107,5 +101,10 @@ ext { jboss_common_core: "org.jboss:jboss-common-core:2.2.16.GA@jar", jnp_client: "org.jboss.naming:jnp-client:${jnpVersion}", jnp_server: "org.jboss.naming:jnpserver:${jnpVersion}", + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~ c3p0 + c3p0: "c3p0:c3p0:0.9.1", + ehcache: "net.sf.ehcache:ehcache-core:2.4.3", + proxool: "proxool:proxool:0.8.3" ] } diff --git a/release/release.gradle b/release/release.gradle index cac16e1949..d86a2427d6 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -1,5 +1,6 @@ apply plugin: 'base' apply plugin: 'idea' +apply from: "../utilities.gradle" buildDir = "target" @@ -83,21 +84,6 @@ task aggregateJavadocs(type: Javadoc) { } } -String determinePackageName(SourceDirectorySet sourceDirectorySet, File javaFile) { - final javaFileAbsolutePath = javaFile.absolutePath; - for ( File sourceDirectory : sourceDirectorySet.srcDirs ) { - final String sourceDirectoryAbsolutePath = sourceDirectory.absolutePath; - if ( javaFileAbsolutePath.startsWith( sourceDirectoryAbsolutePath ) ) { - final String javaFileRelativePath = javaFileAbsolutePath.substring( - sourceDirectoryAbsolutePath.length() + 1, - javaFileAbsolutePath.lastIndexOf( File.separator ) - ); - return javaFileRelativePath.replace( File.separator, "." ); - } - } - throw new RuntimeException( "ugh" ); -} - aggregateJavadocs.doLast { copy { from new File( projectDir, 'src/javadoc/images' ) diff --git a/settings.gradle b/settings.gradle index 2e17736604..c6fb9085db 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,8 @@ include 'hibernate-testing' include 'hibernate-entitymanager' include 'hibernate-envers' +include 'hibernate-osgi' + include 'hibernate-c3p0' include 'hibernate-proxool' diff --git a/utilities.gradle b/utilities.gradle new file mode 100644 index 0000000000..5dc22c6e2e --- /dev/null +++ b/utilities.gradle @@ -0,0 +1,49 @@ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ + +apply plugin: UtilitiesPlugin + +class UtilitiesPlugin implements Plugin { + def void apply(Object project) { + project.convention.plugins.utilities = new UtilitiesPluginDef() + } +} + +class UtilitiesPluginDef { + public String determinePackageName(SourceDirectorySet sourceDirectorySet, File javaFile) { + final javaFileAbsolutePath = javaFile.absolutePath; + for ( File sourceDirectory : sourceDirectorySet.srcDirs ) { + final String sourceDirectoryAbsolutePath = sourceDirectory.absolutePath; + if ( javaFileAbsolutePath.startsWith( sourceDirectoryAbsolutePath ) ) { + final String javaFileRelativePath = javaFileAbsolutePath.substring( + sourceDirectoryAbsolutePath.length() + 1, + javaFileAbsolutePath.lastIndexOf( File.separator ) + ); + return javaFileRelativePath.replace( File.separator, "." ); + } + } + throw new RuntimeException( "ugh" ); + } +}