I've solved the problem - though, I'm not quite happy with it. Indeed there seems to be a requirement for a custom composite user type to have a non-<set> collection of values within a joined-subclass. So the only way is writing a usertype for all values. I see this is better style to do with all user types anyways. But for quick and dirty testing i prefer to do the mapping with <composite-element>.
Any plans to support this without the need of a custom user type???
Anyways, here's the complete code for a Phone(Number) value and the related usertype as well as mapping examples. Hope someone can use it.
best,
andi
Phone (number) value
Code:
import java.io.Serializable;
/**
* Value that identifies and typifies a phone number.
* <p>
* A phone number can be a phone, fax, cellphone, or data line and consists of
* the following parts:
* <p>
* <strong>Type of phone</strong><br />
* Type of data per {@link PhoneType}.
* <p>
* <strong>Country Code</strong><br />
* One to three digit international calling code country designation as
* specified by ITU (International Telecommunication Union) E.164.
* See http://www.itu.int/itudoc/itu-t/ob-lists/icc/e164_763.html.
* <p>
* <strong>Area Code</strong><br />
* For US numbers, this is the 3-digit NANP (North American Numbering Plan)
* initial portion of a domestic telephone number, corresponding to a region,
* city or part of a city. For a foreign address this can be a city code.
* <p>
* <strong>Number</strong><br />
* The remainder of a foreign or domestic number. It will be 7 digits for US
* numbers, variable for foreign.
* <p>
* <strong>Extension</strong><br />
* An optional, numeric extension number. In calling, this is technically not
* a part of the number itself, but an access code used to further route a
* call once a connection has been made. This field can also be used to store
* Dataline specific comments (i.e. 069-121212 Leonardo).
* <p>
* Example: 049 06532 12458 10
* <p>
* Please note that no replacements are done with the stored version. So if you
* need a special formatting use the displayName() methods in this class.
*
* @author Andreas Aderhold, All rights reserved
* @version $Id$
*/
public final class Phone implements Serializable/*, Comparable*/ {
// ~ Fields ----------------------------------------------------------------
private PhoneType type;
/*One to three digit international calling code country designation as specified by ITU (International Telecommunication Union) E.164. See http://www.itu.int/itudoc/itu-t/ob-lists/icc/e164_763.html.*/
private String countrycode;
private String areacode;
private String number;
private String extension;
// ~ Constructors ----------------------------------------------------------
/** Default constructor, do not use */
private Phone() {}
private Phone(PhoneType type, String countrycode, String areacode, String number, String extension) {
this.type = type;
this.countrycode = countrycode;
this.areacode = areacode;
this.number = number;
this.extension = extension;
}
// ~ Factories -------------------------------------------------------------
public static Phone newPhone(PhoneType type, String countrycode, String areacode, String number, String extension) {
return new Phone(type, countrycode, areacode, number, extension);
}
public static Phone newPhone(String countrycode, String areacode, String number, String extension) {
return new Phone(PhoneType.Phone, countrycode, areacode, number, extension);
}
public static Phone newFax(String countrycode, String areacode, String number, String extension) {
return new Phone(PhoneType.Fax, countrycode, areacode, number, extension);
}
public static Phone newCellphone(String countrycode, String areacode, String number, String extension) {
return new Phone(PhoneType.Cellphone, countrycode, areacode, number, extension);
}
// ~ Business methods ------------------------------------------------------
public PhoneType type() {
return this.type;
}
public String countrycode() {
return this.countrycode;
}
public String areacode() {
return this.areacode;
}
public String number() {
return this.number;
}
public String extension() {
return this.extension;
}
public boolean isFax() {
return type.compareTo(PhoneType.Fax) == 0;
}
public boolean isCellphone() {
return type.compareTo(PhoneType.Cellphone) == 0;
}
public boolean isPhone() {
return type.compareTo(PhoneType.Phone) == 0;
}
public boolean isData() {
return type.compareTo(PhoneType.Data) == 0;
}
public String displayName() {
return type.getPhoneType() + ":" + countrycode + '-' + areacode + '-' + number + ' ' + extension;
}
// ~ System methods --------------------------------------------------------
public String toString() {
return displayName();
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Phone)) return false;
final Phone p = (Phone) o;
if (type.compareTo(p.type) != 0) return false;
if (!countrycode.equals(p.countrycode)) return false;
if (!areacode.equals(p.areacode)) return false;
if (!number.equals(p.number)) return false;
return extension.equals(p.extension);
}
public int hashCode() {
int result;
result = type.hashCode();
result = 29 * result + ((areacode != null) ? areacode.hashCode() : 0);
result = 29 * result + ((countrycode != null) ? countrycode.hashCode() : 0);
result = 29 * result + ((number != null) ? number.hashCode() : 0);
result = 29 * result + ((extension != null) ? extension.hashCode() : 0);
return result;
}
// ~ Private Methods -------------------------------------------------------
}
The type enum
Code:
public enum PhoneType {
Phone("Phone"),
Fax("Fax"),
Cellphone("Cellphone"),
Data("Data"),
HomePhone("HomePhone"),
HomeFax("HomeFax");
private final String phonetype;
private PhoneType(String phonetype) {
this.phonetype = phonetype;
}
final String getPhoneType() { return this.phonetype; }
}
Code:
public final class PhoneCompositeUserType implements CompositeUserType {
public Class returnedClass() {
return Phone.class;
}
public boolean equals(Object x, Object y) {
if (x == y) return true;
if (x == null || y == null) return false;
return x.equals(y);
}
public int hashCode(Object o) throws HibernateException {
return o.hashCode();
}
public Serializable disassemble(Object value, SessionImplementor si) throws HibernateException {
return (Serializable) value;
}
public Object assemble(Serializable cached, SessionImplementor si, Object owner) throws HibernateException {
return cached;
}
// TODO: Whats this?
public Object replace(Object original, Object target, SessionImplementor si, Object owner) throws HibernateException {
return null;
}
public Object deepCopy(Object value) {
return value; // Phone vlaue is immutable
}
public boolean isMutable() {
return false;
}
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
if (resultSet.wasNull()) {
return null;
}
String typeName = resultSet.getString(names[0]);
PhoneType type = resultSet.wasNull() ? null : Enum.valueOf(PhoneType.class, typeName);
String countrycode = resultSet.getString(names[1]);
String areacode = resultSet.getString(names[2]);
String number = resultSet.getString(names[3]);
String extension = resultSet.getString(names[4]);
return Phone.newPhone(type, countrycode, areacode, number, extension);
}
public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
statement.setNull(index, Types.VARCHAR);
statement.setNull(index + 1, Types.VARCHAR);
statement.setNull(index + 2, Types.VARCHAR);
statement.setNull(index + 3, Types.VARCHAR);
statement.setNull(index + 4, Types.VARCHAR);
} else {
Phone phone = (Phone) value;
statement.setString(index, ((Enum) phone.type()).name());
statement.setString(index + 1, phone.countrycode());
statement.setString(index + 2, phone.areacode());
statement.setString(index + 3, phone.number());
statement.setString(index + 4, phone.extension());
}
}
public String[] getPropertyNames() {
return new String[] { "type", "countrycode", "areacode", "number", "extension" };
}
public Type[] getPropertyTypes() {
return new Type[] { Hibernate.STRING, Hibernate.STRING, Hibernate.STRING, Hibernate.STRING, Hibernate.STRING };
}
public Object getPropertyValue(Object component, int property) throws HibernateException {
Phone phone = (Phone) component;
if (property == 0) return phone.type();
if (property == 1) return phone.countrycode();
if (property == 2) return phone.areacode();
if (property == 3) return phone.number();
if (property == 4) return phone.extension();
return null;
}
public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
throw new UnsupportedOperationException("Phone is immutable");
}
}
Mapping example:
Code:
<hibernate-mapping package="domain" default-access="field">
<typedef name="PhoneUserType" class="perfectmind.persistence.types.PhoneCompositeUserType" />
<class name="Party" table="PARTIES">
<id name="UID" type="long" column="UID"><generator class="native" /></id>
....
<joined-subclass name="Person" table="PERSONS">
<key column="UID" foreign-key="FK1_PERSONUID"/>
....
<bag name="phones" table="PERSONPHONES">
<key column="PSERONUID"/>
<element type="PhoneUserType">
<column name="TYPE" not-null="true"/>
<column name="COUNTRYCODE" length="3" not-null="true"/>
<column name="AREACODE" not-null="true"/>
<column name="NUMBER" not-null="true"/>
<column name="EXTENSION" not-null="true"/>
</element>
</bag>
</joined-subclass>
</class>
</hibernate-mapping>