diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/PartialDate.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/PartialDate.java
index 4163952c1..ea2c2c902 100644
--- a/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/PartialDate.java
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/PartialDate.java
@@ -1,5 +1,7 @@
package org.navalplanner.business.common.partialtime;
+import java.io.Serializable;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -389,4 +391,16 @@ public class PartialDate implements ReadablePartial {
+ this.granularity);
return get(granularity.getMostSpecific());
}
+
+ public Serializable[] getDataForPersistence() {
+ return new Serializable[] {
+ new Timestamp(normalizedInstant.getMillis()),
+ granularity.name() };
+ }
+
+ public static PartialDate createFromDataForPersistence(Object... values) {
+ Timestamp instant = (Timestamp) values[0];
+ Granularity granularity = Granularity.valueOf((String) values[1]);
+ return PartialDate.createFrom(instant).with(granularity);
+ }
}
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/IntervalOfPartialDatesType.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/IntervalOfPartialDatesType.java
new file mode 100644
index 000000000..d91b660e6
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/IntervalOfPartialDatesType.java
@@ -0,0 +1,135 @@
+package org.navalplanner.business.common.partialtime.hibernate;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import org.hibernate.HibernateException;
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.type.CustomType;
+import org.hibernate.type.Type;
+import org.hibernate.usertype.CompositeUserType;
+import org.navalplanner.business.common.partialtime.IntervalOfPartialDates;
+import org.navalplanner.business.common.partialtime.PartialDate;
+
+public class IntervalOfPartialDatesType implements CompositeUserType {
+
+ private static final String[] PROPERTY_NAMES = { "start", "end" };
+ private final PartialDateType PARTIAL_DATE_TYPE = new PartialDateType();
+
+ @Override
+ public IntervalOfPartialDates assemble(Serializable cached,
+ SessionImplementor session,
+ Object owner) throws HibernateException {
+ Object[] components = (Object[]) cached;
+ PartialDate start = PARTIAL_DATE_TYPE.assemble(
+ (Serializable) components[0], owner);
+ PartialDate end = PARTIAL_DATE_TYPE.assemble(
+ (Serializable) components[1], owner);
+ return new IntervalOfPartialDates(start, end);
+ }
+
+ @Override
+ public Serializable disassemble(Object value, SessionImplementor session)
+ throws HibernateException {
+ IntervalOfPartialDates interval = (IntervalOfPartialDates) value;
+ return new Object[] {
+ PARTIAL_DATE_TYPE.disassemble(interval.getStart()),
+ PARTIAL_DATE_TYPE.disassemble(interval.getEnd()) };
+ }
+
+ @Override
+ public Object deepCopy(Object value) throws HibernateException {
+ return value;
+ }
+
+
+ @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 String[] getPropertyNames() {
+ return PROPERTY_NAMES;
+ }
+
+ @Override
+ public Type[] getPropertyTypes() {
+ Properties emptyProperties = new Properties();
+ CustomType partialDateTypeAsType = new CustomType(
+ PartialDateType.class,
+ emptyProperties);
+ return new Type[] { partialDateTypeAsType, partialDateTypeAsType };
+ }
+
+ @Override
+ public Object getPropertyValue(Object component, int property)
+ throws HibernateException {
+ IntervalOfPartialDates interval = (IntervalOfPartialDates) component;
+ return property == 0 ? interval.getStart() : interval.getEnd();
+ }
+
+ @Override
+ public int hashCode(Object x) throws HibernateException {
+ return x.hashCode();
+ }
+
+ @Override
+ public boolean isMutable() {
+ return false;
+ }
+
+ @Override
+ public Object nullSafeGet(ResultSet rs, String[] names,
+ SessionImplementor session, Object owner)
+ throws HibernateException, SQLException {
+ PartialDate start = PARTIAL_DATE_TYPE.nullSafeGet(rs, subArray(names,
+ 0, 2), owner);
+ PartialDate end = PARTIAL_DATE_TYPE.nullSafeGet(rs, subArray(names, 2,
+ 2), owner);
+ if (start == null || end == null)
+ return null;
+ return new IntervalOfPartialDates(start, end);
+ }
+
+ private static String[] subArray(String[] array, int initialPosition,
+ int size) {
+ String[] result = new String[size];
+ System.arraycopy(array, initialPosition, result, 0, size);
+ return result;
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index,
+ SessionImplementor session) throws HibernateException, SQLException {
+ IntervalOfPartialDates interval = (IntervalOfPartialDates) value;
+ PARTIAL_DATE_TYPE.nullSafeSet(st, interval.getStart(), index);
+ PARTIAL_DATE_TYPE.nullSafeSet(st, interval.getEnd(), index + 2);
+ }
+
+ @Override
+ public Object replace(Object original, Object target,
+ SessionImplementor session, Object owner) throws HibernateException {
+ return original;
+ }
+
+ @Override
+ public Class returnedClass() {
+ return IntervalOfPartialDates.class;
+ }
+
+ @Override
+ public void setPropertyValue(Object component, int property, Object value)
+ throws HibernateException {
+ throw new UnsupportedOperationException(
+ "IntervalOfPartialDates is immutable");
+ }
+
+}
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/PartialDateType.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/PartialDateType.java
new file mode 100644
index 000000000..ae01d818d
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/PartialDateType.java
@@ -0,0 +1,115 @@
+package org.navalplanner.business.common.partialtime.hibernate;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.usertype.UserType;
+import org.navalplanner.business.common.partialtime.PartialDate;
+
+/**
+ * Persists a {@link PartialDate} through hibernate.
+ * @author Óscar González Fernández
+ */
+public class PartialDateType implements UserType {
+ // TODO consider the possibility of using an integer for storing the enum
+ private static final int[] SQL_TYPES = { Types.TIMESTAMP, Types.VARCHAR };
+
+ @Override
+ public int[] sqlTypes() {
+ return SQL_TYPES;
+ }
+
+ @Override
+ public Class returnedClass() {
+ return PartialDate.class;
+ }
+
+ public static class CachedRepresentation implements Serializable {
+ private final Serializable[] fields;
+
+ private CachedRepresentation(Serializable[] fields) {
+ this.fields = fields;
+ }
+
+ public PartialDate toOriginal() {
+ return PartialDate.createFromDataForPersistence(fields[0],
+ fields[1]);
+ }
+ }
+
+ @Override
+ public PartialDate assemble(Serializable cached, Object owner)
+ throws HibernateException {
+ CachedRepresentation representation = (CachedRepresentation) cached;
+ return representation.toOriginal();
+ }
+
+ @Override
+ public Serializable disassemble(Object value) throws HibernateException {
+ PartialDate partialDate = (PartialDate) value;
+ return new CachedRepresentation(partialDate.getDataForPersistence());
+ }
+
+ public Object deepCopy(Object value) throws HibernateException {
+ return value;
+ }
+
+ @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 boolean isMutable() {
+ return false;
+ }
+
+ @Override
+ public PartialDate nullSafeGet(ResultSet rs, String[] names, Object owner)
+ throws HibernateException, SQLException {
+ Timestamp timestamp = (Timestamp) Hibernate.TIMESTAMP.nullSafeGet(rs,
+ names[0]);
+ String granularity = (String) Hibernate.STRING
+ .nullSafeGet(rs, names[1]);
+ if (timestamp == null || granularity == null)
+ return null;
+ return PartialDate.createFromDataForPersistence(timestamp, granularity);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index)
+ throws HibernateException, SQLException {
+ Timestamp timeToSet = null;
+ String granularityToSet = null;
+ PartialDate partialDate = (PartialDate) value;
+ if (partialDate != null) {
+ Serializable[] dataForPersistence = partialDate
+ .getDataForPersistence();
+ timeToSet = (Timestamp) dataForPersistence[0];
+ granularityToSet = (String) dataForPersistence[1];
+ }
+ Hibernate.TIMESTAMP.nullSafeSet(st, timeToSet, index);
+ Hibernate.STRING.nullSafeSet(st, granularityToSet, index + 1);
+ }
+
+ @Override
+ public Object replace(Object original, Object target, Object owner)
+ throws HibernateException {
+ return original;
+ }
+}
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/TimeQuantityType.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/TimeQuantityType.java
new file mode 100644
index 000000000..0fa7903a8
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/partialtime/hibernate/TimeQuantityType.java
@@ -0,0 +1,111 @@
+package org.navalplanner.business.common.partialtime.hibernate;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Iterator;
+
+import net.sf.json.JSONObject;
+
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.usertype.UserType;
+import org.navalplanner.business.common.partialtime.TimeQuantity;
+import org.navalplanner.business.common.partialtime.PartialDate.Granularity;
+
+public class TimeQuantityType implements UserType {
+
+ private static final int[] SQL_TYPES = { Types.VARCHAR };
+
+ @Override
+ public int[] sqlTypes() {
+ return SQL_TYPES;
+ }
+
+ private static String asString(TimeQuantity timeQuantity) {
+ JSONObject jsonObject = new JSONObject();
+ for (Granularity granularity : Granularity.values()) {
+ Integer value = timeQuantity.valueFor(granularity);
+ if (value != 0)
+ jsonObject.put(granularity.name(), value);
+ }
+ return jsonObject.toString();
+ }
+
+ private static TimeQuantity fromString(String timeQuantityAsString) {
+ JSONObject jsonObject = JSONObject.fromObject(timeQuantityAsString);
+ Iterator keys = jsonObject.keys();
+ TimeQuantity result = TimeQuantity.empty();
+ while(keys.hasNext()){
+ Object key = keys.next();
+ Object object = jsonObject.get(key);
+ result = result.plus((Integer) object, Granularity
+ .valueOf((String) key));
+ }
+ return result;
+ }
+
+ @Override
+ public Object assemble(Serializable cached, Object owner)
+ throws HibernateException {
+ return fromString((String) cached);
+ }
+
+ @Override
+ public Serializable disassemble(Object value) throws HibernateException {
+ return asString((TimeQuantity) value);
+ }
+
+ @Override
+ public Object deepCopy(Object value) throws HibernateException {
+ return value;
+ }
+
+
+ @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 boolean isMutable() {
+ return false;
+ }
+
+ @Override
+ public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
+ throws HibernateException, SQLException {
+ String timeQuantityAsString = (String) Hibernate.STRING.nullSafeGet(rs,
+ names);
+ return fromString(timeQuantityAsString);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index)
+ throws HibernateException, SQLException {
+ Hibernate.STRING.nullSafeSet(st, asString((TimeQuantity) value), index);
+ }
+
+ @Override
+ public Object replace(Object original, Object target, Object owner)
+ throws HibernateException {
+ return original;
+ }
+
+ @Override
+ public Class returnedClass() {
+ return TimeQuantity.class;
+ }
+
+}
diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingIntervalOfPartialDates.java b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingIntervalOfPartialDates.java
new file mode 100644
index 000000000..41d9f7609
--- /dev/null
+++ b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingIntervalOfPartialDates.java
@@ -0,0 +1,37 @@
+package org.navalplanner.business.common.test.partialtime;
+
+import org.navalplanner.business.common.partialtime.IntervalOfPartialDates;
+
+public class EntityContainingIntervalOfPartialDates {
+
+ private Long id;
+
+ private Long version;
+
+ private IntervalOfPartialDates interval;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public IntervalOfPartialDates getInterval() {
+ return interval;
+ }
+
+ public void setInterval(IntervalOfPartialDates interval) {
+ this.interval = interval;
+ }
+
+}
diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingPartialDate.java b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingPartialDate.java
new file mode 100644
index 000000000..22b6b57f6
--- /dev/null
+++ b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingPartialDate.java
@@ -0,0 +1,38 @@
+package org.navalplanner.business.common.test.partialtime;
+
+import org.navalplanner.business.common.partialtime.PartialDate;
+
+public class EntityContainingPartialDate {
+
+ private Long id;
+
+ private Long version;
+
+ private PartialDate partialDate;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public PartialDate getPartialDate() {
+ return partialDate;
+ }
+
+ public void setPartialDate(PartialDate partialDate) {
+ this.partialDate = partialDate;
+ }
+
+
+}
diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingTimeQuantity.java b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingTimeQuantity.java
new file mode 100644
index 000000000..9c0a190b6
--- /dev/null
+++ b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/EntityContainingTimeQuantity.java
@@ -0,0 +1,37 @@
+package org.navalplanner.business.common.test.partialtime;
+
+import org.navalplanner.business.common.partialtime.TimeQuantity;
+
+public class EntityContainingTimeQuantity {
+
+ private Long id;
+
+ private Long version;
+
+ private TimeQuantity timeQuantity;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public TimeQuantity getTimeQuantity() {
+ return timeQuantity;
+ }
+
+ public void setTimeQuantity(TimeQuantity timeQuantity) {
+ this.timeQuantity = timeQuantity;
+ }
+
+}
diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/PartialDateRelatedClassesHibernateMappingTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/PartialDateRelatedClassesHibernateMappingTest.java
new file mode 100644
index 000000000..0f05a35ca
--- /dev/null
+++ b/navalplanner-business/src/test/java/org/navalplanner/business/common/test/partialtime/PartialDateRelatedClassesHibernateMappingTest.java
@@ -0,0 +1,79 @@
+package org.navalplanner.business.common.test.partialtime;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
+import static org.navalplanner.business.test.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_TEST_FILE;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.joda.time.LocalDate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.navalplanner.business.common.partialtime.IntervalOfPartialDates;
+import org.navalplanner.business.common.partialtime.PartialDate;
+import org.navalplanner.business.common.partialtime.TimeQuantity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.annotation.Transactional;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE,
+ BUSINESS_SPRING_CONFIG_TEST_FILE })
+@Transactional
+public class PartialDateRelatedClassesHibernateMappingTest {
+
+ @Autowired
+ private SessionFactory sessionFactory;
+
+ public Session getSession() {
+ return sessionFactory.getCurrentSession();
+ }
+
+ @Test
+ public void partialDatesCanBeSavedAndRetrieved() {
+ EntityContainingPartialDate entity = new EntityContainingPartialDate();
+ PartialDate partialDate = PartialDate.createFrom(new LocalDate(2000, 4,
+ 20));
+ entity.setPartialDate(partialDate);
+ getSession().save(entity);
+ getSession().flush();
+ getSession().evict(entity);
+ EntityContainingPartialDate reloaded = (EntityContainingPartialDate) getSession()
+ .get(EntityContainingPartialDate.class, entity.getId());
+ assertThat(reloaded.getPartialDate(), equalTo(partialDate));
+ }
+
+ @Test
+ public void intervalsCanBeSavedAndRetrieved() {
+ EntityContainingIntervalOfPartialDates entity = new EntityContainingIntervalOfPartialDates();
+ PartialDate start = PartialDate.createFrom(new LocalDate(2000, 4, 20));
+ PartialDate end = PartialDate.createFrom(new LocalDate(2001, 4, 23));
+ IntervalOfPartialDates original = new IntervalOfPartialDates(start, end);
+ entity.setInterval(original);
+ getSession().save(entity);
+ getSession().flush();
+ getSession().evict(entity);
+ EntityContainingIntervalOfPartialDates reloaded = (EntityContainingIntervalOfPartialDates) getSession()
+ .get(
+ EntityContainingIntervalOfPartialDates.class, entity.getId());
+ assertThat(reloaded.getInterval(), equalTo(original));
+ }
+
+ @Test
+ public void timeQuantitysCanBeSavedAndRetrieved() {
+ EntityContainingTimeQuantity entity = new EntityContainingTimeQuantity();
+ TimeQuantity duration = new IntervalOfPartialDates(PartialDate.createFrom(new LocalDate(2000, 4, 20)), PartialDate.createFrom(new LocalDate(2001, 4, 23)))
+ .getDuration();
+ entity.setTimeQuantity(duration);
+ getSession().save(entity);
+ getSession().flush();
+ getSession().evict(entity);
+ EntityContainingTimeQuantity reloaded = (EntityContainingTimeQuantity) getSession()
+ .get(
+ EntityContainingTimeQuantity.class, entity.getId());
+ TimeQuantity timeQuantity = reloaded.getTimeQuantity();
+ assertThat(timeQuantity, equalTo(duration));
+ }
+}
diff --git a/navalplanner-business/src/test/resources/TestEntities.hbm.xml b/navalplanner-business/src/test/resources/TestEntities.hbm.xml
new file mode 100644
index 000000000..d33117824
--- /dev/null
+++ b/navalplanner-business/src/test/resources/TestEntities.hbm.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml b/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml
index 78946c8fd..be89b5f59 100644
--- a/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml
+++ b/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml
@@ -32,6 +32,9 @@
org/navalplanner/business/orders/entities/Orders.hbm.xml
+
+ TestEntities.hbm.xml
+