1 package com.vladmihalcea.hibernate.type.range;
2
3 import com.vladmihalcea.hibernate.type.ImmutableType;
4 import com.vladmihalcea.hibernate.util.ReflectionUtils;
5 import org.hibernate.HibernateException;
6 import org.hibernate.annotations.common.reflection.XProperty;
7 import org.hibernate.annotations.common.reflection.java.JavaXMember;
8 import org.hibernate.engine.spi.SharedSessionContractImplementor;
9 import org.hibernate.usertype.DynamicParameterizedType;
10
11 import java.lang.reflect.ParameterizedType;
12 import java.lang.reflect.Type;
13 import java.math.BigDecimal;
14 import java.sql.PreparedStatement;
15 import java.sql.ResultSet;
16 import java.sql.SQLException;
17 import java.sql.Types;
18 import java.time.LocalDate;
19 import java.time.LocalDateTime;
20 import java.time.ZonedDateTime;
21 import java.util.Properties;
22
23 /**
24  * Maps a {@link Range} object type to a PostgreSQL <a href="https://www.postgresql.org/docs/current/rangetypes.html">range</a>
25  * column type.
26  * <p>
27  * Supported range types:
28  * <ul>
29  * <li>int4range</li>
30  * <li>int8range</li>
31  * <li>numrange</li>
32  * <li>tsrange</li>
33  * <li>tstzrange</li>
34  * <li>daterange</li>
35  * </ul>
36  * <p>
37  * For more details about how to use it,
38  * check out <a href="https://vladmihalcea.com/map-postgresql-range-column-type-jpa-hibernate/">this article</a>
39  * on <a href="https://vladmihalcea.com/">vladmihalcea.com</a>.
40  *
41  * @author Edgar Asatryan
42  * @author Vlad Mihalcea
43  */

44 public class PostgreSQLRangeType extends ImmutableType<Range> implements DynamicParameterizedType {
45
46     public static final PostgreSQLRangeType INSTANCE = new PostgreSQLRangeType();
47
48     private Type type;
49
50     public PostgreSQLRangeType() {
51         super(Range.class);
52     }
53
54     @Override
55     public int[] sqlTypes() {
56         return new int[]{Types.OTHER};
57     }
58
59     @Override
60     protected Range get(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException {
61         Object pgObject = rs.getObject(names[0]);
62
63         if (pgObject == null) {
64             return null;
65         }
66
67         String type = ReflectionUtils.invokeGetter(pgObject, "type");
68         String value = ReflectionUtils.invokeGetter(pgObject, "value");
69
70         switch (type) {
71             case "int4range":
72                 return Range.integerRange(value);
73             case "int8range":
74                 return Range.longRange(value);
75             case "numrange":
76                 return Range.bigDecimalRange(value);
77             case "tsrange":
78                 return Range.localDateTimeRange(value);
79             case "tstzrange":
80                 return Range.zonedDateTimeRange(value);
81             case "daterange":
82                 return Range.localDateRange(value);
83             default:
84                 throw new HibernateException(
85                     new IllegalStateException("The range type [" + type + "] is not supported!")
86                 );
87         }
88     }
89
90     @Override
91     protected void set(PreparedStatement st, Range range, int index, SharedSessionContractImplementor session) throws SQLException {
92
93         if (range == null) {
94             st.setNull(index, Types.OTHER);
95         } else {
96             Object holder = ReflectionUtils.newInstance("org.postgresql.util.PGobject");
97             ReflectionUtils.invokeSetter(holder, "type", determineRangeType(range));
98             ReflectionUtils.invokeSetter(holder, "value", range.asString());
99             st.setObject(index, holder);
100         }
101     }
102
103     private static String determineRangeType(Range<?> range) {
104         Class<?> clazz = range.getClazz();
105
106         if (clazz.equals(Integer.class)) {
107             return "int4range";
108         } else if (clazz.equals(Long.class)) {
109             return "int8range";
110         } else if (clazz.equals(BigDecimal.class)) {
111             return "numrange";
112         } else if (clazz.equals(LocalDateTime.class)) {
113             return "tsrange";
114         } else if (clazz.equals(ZonedDateTime.class)) {
115             return "tstzrange";
116         } else if (clazz.equals(LocalDate.class)) {
117             return "daterange";
118         }
119
120         throw new HibernateException(
121             new IllegalStateException("The class [" + clazz.getName() + "] is not supported!")
122         );
123     }
124
125     @Override
126     public void setParameterValues(Properties parameters) {
127         final XProperty xProperty = (XProperty) parameters.get(DynamicParameterizedType.XPROPERTY);
128         if (xProperty instanceof JavaXMember) {
129             type = ((JavaXMember) xProperty).getJavaType();
130         } else {
131             type = ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass();
132         }
133     }
134
135     public Class<?> getElementType() {
136         return type instanceof ParameterizedType ?
137                 (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0] : null;
138     }
139
140 }
141