View Javadoc
1   /*
2    *  Copyright 2010, 2011 Christopher Pheby
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.jadira.usertype.spi.shared;
17  
18  import static org.jadira.usertype.spi.utils.reflection.ArrayUtils.copyOf;
19  
20  import java.io.Serializable;
21  import java.sql.PreparedStatement;
22  import java.sql.ResultSet;
23  import java.sql.SQLException;
24  import java.util.HashMap;
25  import java.util.Map;
26  
27  import org.hibernate.HibernateException;
28  import org.hibernate.engine.spi.SharedSessionContractImplementor;
29  import org.hibernate.type.Type;
30  import org.hibernate.usertype.CompositeUserType;
31  import org.jadira.usertype.spi.utils.reflection.TypeHelper;
32  
33  public abstract class AbstractMultiColumnUserType<T> extends AbstractUserType implements CompositeUserType, Serializable {
34  
35      private static final long serialVersionUID = -8258683760413283329L;
36  
37      private int[] sqlTypes;
38  
39      private Type[] hibernateTypes;
40  
41      /* DefaultPropertyNames is currently not being used */
42      private String[] defaultPropertyNames;
43  
44  	public AbstractMultiColumnUserType() {
45  
46      	initialise();
47      }
48  
49      protected final void initialise() {
50      	
51          initialiseSqlTypes();
52          initialiseHibernateTypes();
53          initialiseDefaultPropertyNames();
54  	}
55  
56  	private void initialiseDefaultPropertyNames() {
57  		Map<String, Integer> nameCount = new HashMap<String, Integer>();
58  
59          defaultPropertyNames = new String[getColumnMappers().length];
60          for (int i = 0; i < defaultPropertyNames.length; i++) {
61              String className = hibernateTypes[i].getClass().getSimpleName();
62              if (className.endsWith("Type")) {
63                  className = className.substring(0, className.length() - 4);
64              }
65  
66              String name = className.toLowerCase();
67              final Integer count;
68              if (nameCount.containsKey(name)) {
69                  Integer oldCount = nameCount.get(name);
70                  count = oldCount.intValue() + 1;
71                  defaultPropertyNames[i] = name + count;
72              } else {
73                  count = 1;
74                  defaultPropertyNames[i] = name;
75              }
76              nameCount.put(name, count);
77          }
78  	}
79  
80  	@SuppressWarnings({ "unchecked", "rawtypes" })
81  	private void initialiseHibernateTypes() {
82  		hibernateTypes = new Type[getColumnMappers().length];
83          for (int i = 0; i < hibernateTypes.length; i++) {
84              hibernateTypes[i] = new ColumnMapperSingleColumnTypeAdapter(getColumnMappers()[i]);
85          }
86  	}
87  
88  	private void initialiseSqlTypes() {
89  		sqlTypes = new int[getColumnMappers().length];
90          for (int i = 0; i < sqlTypes.length; i++) {
91              sqlTypes[i] = getColumnMappers()[i].getSqlType();
92          }
93  	}
94  
95  	public int[] sqlTypes() {
96          return copyOf(sqlTypes);
97      }
98  
99      @SuppressWarnings("unchecked")
100     @Override
101     public Class<T> returnedClass() {
102         return (Class<T>) TypeHelper.getTypeArguments(AbstractMultiColumnUserType.class, getClass()).get(0);
103     }
104 
105     protected abstract ColumnMapper<?, ?>[] getColumnMappers();
106 
107     @SuppressWarnings({ "rawtypes", "unchecked" })
108     @Override
109     public T nullSafeGet(ResultSet resultSet, String[] strings, SharedSessionContractImplementor session, Object object) throws SQLException {
110 
111     	beforeNullSafeOperation(session);
112     	
113     	final SharedSessionContractImplementor mySession = doWrapSession(session);
114     	
115     	try {
116 	        Object[] convertedColumns = new Object[getColumnMappers().length];
117 	
118 	        for (int getIndex = 0; getIndex < getColumnMappers().length; getIndex++) {
119 	            ColumnMapper nextMapper = getColumnMappers()[getIndex];
120 	
121 	            final Object converted = nextMapper.getHibernateType().nullSafeGet(resultSet, strings[getIndex], mySession, object);
122 	
123 	            if (converted != null) {
124 	                convertedColumns[getIndex] = nextMapper.fromNonNullValue(converted);
125 	            }
126 	        }
127 	
128 	        for (int i = 0; i < convertedColumns.length; i++) {
129 	            if (convertedColumns[i] != null) {
130 	                return fromConvertedColumns(convertedColumns);
131 	            }
132 	        }
133 	
134 	        return null;
135 	        
136     	} finally {
137     		afterNullSafeOperation(session);
138     	}
139     }
140 
141     protected abstract T fromConvertedColumns(Object[] convertedColumns);
142 
143     protected abstract Object[] toConvertedColumns(T value);
144 
145     @SuppressWarnings("unchecked")
146     @Override
147     public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SharedSessionContractImplementor session) throws SQLException {
148 
149     	beforeNullSafeOperation(session);
150     	
151     	final SharedSessionContractImplementor mySession = doWrapSession(session);
152     	
153     	try {
154 	        final Object[] valuesToSet = new Object[getColumnMappers().length];
155 	
156 	        if (value != null) {
157 	
158 	            final T myValue = (T) value;
159 	            Object[] convertedColumns = toConvertedColumns(myValue);
160 	
161 	            for (int cIdx = 0; cIdx < valuesToSet.length; cIdx++) {
162 	
163 	                @SuppressWarnings("rawtypes") ColumnMapper nextMapper = getColumnMappers()[cIdx];
164 	                valuesToSet[cIdx] = nextMapper.toNonNullValue(convertedColumns[cIdx]);
165 	            }
166 	        }
167 	
168 	        for (int setIndex = 0; setIndex < valuesToSet.length; setIndex++) {
169 	
170 	            @SuppressWarnings("rawtypes") ColumnMapper nextMapper = getColumnMappers()[setIndex];
171 
172 	            // TODO Still need to work out where an adjuster will be injected
173 	            nextMapper.getHibernateType().nullSafeSet(preparedStatement, valuesToSet[setIndex], index + setIndex, mySession);
174 	        }
175 	        
176 		} finally {
177 			afterNullSafeOperation(session);
178 		}
179     }
180 
181 	protected SharedSessionContractImplementor doWrapSession(SharedSessionContractImplementor session) {
182 		return session;
183 	}
184     
185     @Override
186     public String[] getPropertyNames() {
187         return defaultPropertyNames;
188     }
189 
190     @Override
191     public Type[] getPropertyTypes() {
192         return copyOf(hibernateTypes);
193     }
194 
195     @Override
196     public Object getPropertyValue(Object component, int property) throws HibernateException {
197 
198         if (!returnedClass().isAssignableFrom(component.getClass())) {
199             throw new HibernateException("getPropertyValue called with incorrect class: {" + component.getClass() + "}");
200         }
201         @SuppressWarnings("unchecked") Object[] cols = toConvertedColumns((T) component);
202         return cols[property];
203     }
204 
205     @Override
206     public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
207         throw new HibernateException("Called setPropertyValue on an immutable type {" + component.getClass() + "}");
208     }
209 
210     @Override
211     public Serializable disassemble(Object value, SharedSessionContractImplementor session) throws HibernateException {
212         return super.disassemble(value);
213     }
214 
215     @Override
216     public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException {
217         return super.assemble(cached, owner);
218     }
219 
220     @Override
221     public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner) throws HibernateException {
222         return super.replace(original, target, owner);
223     }
224 }