Author: epbernard
Date: 2010-09-27 18:07:44 -0400 (Mon, 27 Sep 2010)
New Revision: 20728
Added:
core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpression.java
core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpressions.java
core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpression.java
core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpressions.java
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ReadWriteExpressionTest.java
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java
Modified:
core/trunk/core/src/main/java/org/hibernate/cfg/Ejb3Column.java
core/trunk/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml
Log:
HHH-4510 Add support for column-levle custom read/write expression
Added: core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpression.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpression.java
(rev 0)
+++ core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpression.java 2010-09-27
22:07:44 UTC (rev 20728)
@@ -0,0 +1,51 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Custom SQL expression used to read the value from a column. True for direct object
loading as well as queries.
+ *
+ * For example: <code>decrypt(credit_card_num)</code>
+ *
+ * @author Emmanuel Bernard
+ */
+(a)java.lang.annotation.Target({FIELD,METHOD})
+@Retention(RUNTIME)
+public @interface ReadExpression {
+ /**
+ * (Logical) column name for which the expression is used
+ */
+ String forColumn();
+
+ /**
+ * Custom SQL expression used to read from the column
+ */
+ String expression();
+}
Added: core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpressions.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpressions.java
(rev 0)
+++
core/trunk/core/src/main/java/org/hibernate/annotations/ReadExpressions.java 2010-09-27
22:07:44 UTC (rev 20728)
@@ -0,0 +1,42 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Plural annotation for @ReadExpression.
+ * Useful when more than one column is using this behavior.
+ *
+ * @author Emmanuel Bernard
+ */
+(a)java.lang.annotation.Target({FIELD,METHOD})
+@Retention(RUNTIME)
+public @interface ReadExpressions {
+ ReadExpression[] value();
+}
Added: core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpression.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpression.java
(rev 0)
+++
core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpression.java 2010-09-27
22:07:44 UTC (rev 20728)
@@ -0,0 +1,55 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Custom SQL expression used to write the value to a column. True for direct object
saving as well as insert queries.
+ * The write expression must contain exactly one '?' placeholder for the value.
+ *
+ * For example: <code>encrypt(?)</code>
+ *
+ *
+ *
+ * @author Emmanuel Bernard
+ */
+(a)java.lang.annotation.Target({FIELD,METHOD})
+@Retention(RUNTIME)
+public @interface WriteExpression {
+ /**
+ * (Logical) column name for which the expression is used
+ */
+ String forColumn();
+
+ /**
+ * Custom SQL expression used to write to the column.
+ * The write expression must contain exactly one '?' placeholder for the value.
+ */
+ String expression();
+}
Added: core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpressions.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpressions.java
(rev 0)
+++
core/trunk/core/src/main/java/org/hibernate/annotations/WriteExpressions.java 2010-09-27
22:07:44 UTC (rev 20728)
@@ -0,0 +1,42 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Plural annotation for @WriteExpression.
+ * Useful when more than one column is using this behavior.
+ *
+ * @author Emmanuel Bernard
+ */
+(a)java.lang.annotation.Target({FIELD,METHOD})
+@Retention(RUNTIME)
+public @interface WriteExpressions {
+ WriteExpression[] value();
+}
Modified: core/trunk/core/src/main/java/org/hibernate/cfg/Ejb3Column.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/cfg/Ejb3Column.java 2010-09-27 17:33:29
UTC (rev 20727)
+++ core/trunk/core/src/main/java/org/hibernate/cfg/Ejb3Column.java 2010-09-27 22:07:44
UTC (rev 20728)
@@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
- * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
+ * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates 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.
+ * 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
@@ -28,6 +28,11 @@
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.Index;
+import org.hibernate.annotations.ReadExpression;
+import org.hibernate.annotations.ReadExpressions;
+import org.hibernate.annotations.WriteExpression;
+import org.hibernate.annotations.WriteExpressions;
+import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.cfg.annotations.Nullability;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Formula;
@@ -66,6 +71,8 @@
private String formulaString;
private Formula formula;
private Table table;
+ private String readExpression;
+ private String writeExpression;
public void setTable(Table table) {
this.table = table;
@@ -213,6 +220,19 @@
this.mappingColumn.setNullable( nullable );
this.mappingColumn.setSqlType( sqlType );
this.mappingColumn.setUnique( unique );
+
+ if(writeExpression != null &&
!writeExpression.matches("[^?]*\\?[^?]*")) {
+ throw new AnnotationException(
+ "@WriteExpression must contain exactly one value placeholder ('?')
character: property ["
+ + propertyName + "] and column [" + logicalColumnName + "]"
+ );
+ }
+ if ( readExpression != null) {
+ this.mappingColumn.setCustomRead( readExpression );
+ }
+ if ( writeExpression != null) {
+ this.mappingColumn.setCustomWrite( writeExpression );
+ }
}
}
@@ -451,6 +471,7 @@
column.setPropertyHolder( propertyHolder );
column.setJoins( secondaryTables );
column.setMappings( mappings );
+ column.extractDataFromPropertyData(inferredData);
column.bind();
columns[index] = column;
}
@@ -459,6 +480,45 @@
return columns;
}
+ //must only be called after all setters are defined and before bind
+ private void extractDataFromPropertyData(PropertyData inferredData) {
+ if ( inferredData != null ) {
+ XProperty property = inferredData.getProperty();
+ if ( property != null ) {
+ {
+ processReadExpression( property.getAnnotation( ReadExpression.class ) );
+ ReadExpressions annotations = property.getAnnotation( ReadExpressions.class );
+ if (annotations != null) {
+ for ( ReadExpression annotation : annotations.value() ) {
+ processReadExpression( annotation );
+ }
+ }
+ }
+ {
+ processWriteExpression( property.getAnnotation( WriteExpression.class ) );
+ WriteExpressions annotations = property.getAnnotation( WriteExpressions.class );
+ if (annotations != null) {
+ for ( WriteExpression annotation : annotations.value() ) {
+ processWriteExpression( annotation );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void processReadExpression(ReadExpression annotation) {
+ if ( annotation != null && annotation.forColumn().equals( logicalColumnName ) )
{
+ readExpression = annotation.expression();
+ }
+ }
+
+ private void processWriteExpression(WriteExpression annotation) {
+ if ( annotation != null && annotation.forColumn().equals( logicalColumnName ) )
{
+ writeExpression = annotation.expression();
+ }
+ }
+
private static Ejb3Column[] buildImplicitColumn(
PropertyData inferredData,
String suffixForDefaultColumnName,
@@ -493,6 +553,7 @@
else {
column.setImplicit( true );
}
+ column.extractDataFromPropertyData( inferredData );
column.bind();
return columns;
}
Modified:
core/trunk/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml
===================================================================
---
core/trunk/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml 2010-09-27
17:33:29 UTC (rev 20727)
+++
core/trunk/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml 2010-09-27
22:07:44 UTC (rev 20728)
@@ -5066,9 +5066,9 @@
<listitem>
<para>a <literal>hibernate namespace</literal> is
recognized
whenever the resolver encounters a systemId starting with
- <
literal>http://www.hibernate.org/dtd/</literal>. The
- resolver attempts to resolve these entities via the classloader
- which loaded the Hibernate classes.</para>
+ <
literal>http://www.hibernate.org/dtd/</literal>. The resolver
+ attempts to resolve these entities via the classloader which
+ loaded the Hibernate classes.</para>
</listitem>
<listitem>
@@ -5777,16 +5777,37 @@
the values of columns mapped to <link
linkend="mapping-declaration-property">simple properties</link>.
For
example, if your database provides a set of data encryption functions, you
- can invoke them for individual columns like this:
-</para>
-<programlisting
- role="XML"><property name="creditCardNumber">
+ can invoke them for individual columns like this:</para>
+
+ <programlisting role="JAVA">@Entity
+class CreditCard {
+ @Column(name="credit_card_num")
+ @ReadExpression(
+ forColumn="credit_card_num",
+ expression="decrypt(credit_card_num)")
+ @WriteExpression(
+ forColumn="credit_card_num",
+ expression="encrypt(?)")
+ public String getCreditCardNumber() { return creditCardNumber; }
+ public void setCreditCardNumber(String number) { this.creditCardNumber = number; }
+ private String creditCardNumber;
+}</programlisting>
+
+ <para>or in XML</para>
+
+ <programlisting role="XML"><property
name="creditCardNumber">
<column
name="credit_card_num"
read="decrypt(credit_card_num)"
write="encrypt(?)"/>
</property></programlisting>
+ <note>
+ <para>When using <classname>@ReadExpression</classname> /
+ <classname>@WriteExpression</classname> annotations, you must
explicitly
+ declare the <literal>(a)Column.name</literal> property.</para>
+ </note>
+
<para>Hibernate applies the custom expressions automatically whenever the
property is referenced in a query. This functionality is similar to a
derived-property <literal>formula</literal> with two differences:
@@ -5803,10 +5824,6 @@
<para>The <literal>write</literal> expression, if specified, must
contain
exactly one '?' placeholder for the value.</para>
-
- <note>
- <para>This feature is not supported in Annotations</para>
- </note>
</section>
<section id="mapping-database-object">
Added:
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ReadWriteExpressionTest.java
===================================================================
---
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ReadWriteExpressionTest.java
(rev 0)
+++
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/ReadWriteExpressionTest.java 2010-09-27
22:07:44 UTC (rev 20728)
@@ -0,0 +1,88 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates 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.various.readwriteexpression;
+
+import java.util.Date;
+
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.test.annotations.TestCase;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class ReadWriteExpressionTest extends TestCase {
+
+ public void testCustomColumnReadAndWrite() throws Exception{
+ Session s = openSession();
+ Transaction t = s.beginTransaction();
+ final double HEIGHT_INCHES = 73;
+ final double HEIGHT_CENTIMETERS = HEIGHT_INCHES * 2.54d;
+
+ Staff staff = new Staff(HEIGHT_INCHES, HEIGHT_INCHES, 1);
+ s.persist( staff );
+ s.flush();
+
+ // Test value conversion during insert
+ Double heightViaSql = (Double)s.createSQLQuery("select size_in_cm from t_staff
where t_staff.id=1").uniqueResult();
+ assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d);
+
+ heightViaSql = (Double)s.createSQLQuery("select radiusS from t_staff where
t_staff.id=1").uniqueResult();
+ assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d);
+
+ // Test projection
+ Double heightViaHql = (Double)s.createQuery("select s.sizeInInches from Staff s
where s.id = 1").uniqueResult();
+ assertEquals(HEIGHT_INCHES, heightViaHql, 0.01d);
+
+ // Test restriction and entity load via criteria
+ staff = (Staff)s.createCriteria(Staff.class)
+ .add( Restrictions.between("sizeInInches", HEIGHT_INCHES - 0.01d,
HEIGHT_INCHES + 0.01d))
+ .uniqueResult();
+ assertEquals(HEIGHT_INCHES, staff.getSizeInInches(), 0.01d);
+
+ // Test predicate and entity load via HQL
+ staff = (Staff)s.createQuery("from Staff s where s.sizeInInches between ? and
?")
+ .setDouble(0, HEIGHT_INCHES - 0.01d)
+ .setDouble(1, HEIGHT_INCHES + 0.01d)
+ .uniqueResult();
+ assertEquals(HEIGHT_INCHES, staff.getSizeInInches(), 0.01d);
+
+ // Test update
+ staff.setSizeInInches(1);
+ s.flush();
+ heightViaSql = (Double)s.createSQLQuery("select size_in_cm from t_staff where
t_staff.id=1").uniqueResult();
+ assertEquals(2.54d, heightViaSql, 0.01d);
+ s.delete(staff);
+ t.commit();
+ s.close();
+ }
+
+ @Override
+ protected Class<?>[] getAnnotatedClasses() {
+ return new Class<?>[] {
+ Staff.class
+ };
+ }
+}
Added:
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java
===================================================================
---
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java
(rev 0)
+++
core/trunk/testsuite/src/test/java/org/hibernate/test/annotations/various/readwriteexpression/Staff.java 2010-09-27
22:07:44 UTC (rev 20728)
@@ -0,0 +1,65 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates 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.various.readwriteexpression;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import org.hibernate.annotations.ReadExpression;
+import org.hibernate.annotations.WriteExpression;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+@Table(name="t_staff")
+public class Staff {
+
+ public Staff(double sizeInInches, double radius, Integer id) {
+ this.sizeInInches = sizeInInches;
+ this.radiusS = radius;
+ this.id = id;
+ }
+
+ @Id
+ public Integer getId() { return id; }
+ public void setId(Integer id) { this.id = id; }
+ private Integer id;
+
+ @Column(name="size_in_cm")
+ @ReadExpression( forColumn = "size_in_cm", expression = "size_in_cm /
2.54" )
+ @WriteExpression( forColumn = "size_in_cm", expression = "? * 2.54"
)
+ public double getSizeInInches() { return sizeInInches; }
+ public void setSizeInInches(double sizeInInches) { this.sizeInInches = sizeInInches; }
+ private double sizeInInches;
+
+ @Column(name="radiusS")
+ @ReadExpression( forColumn = "radiusS", expression = "radiusS /
2.54" )
+ @WriteExpression( forColumn = "radiusS", expression = "? * 2.54" )
+ public double getRadiusS() { return radiusS; }
+ public void setRadiusS(double radiusS) { this.radiusS = radiusS; }
+ private double radiusS;
+}