commit | author | age
|
c387eb
|
1 |
/* |
U |
2 |
* BaseLink - Generic object relational mapping |
|
3 |
* Copyright (C) 2011 Ulrich Hilger, http://uhilger.de |
|
4 |
* |
|
5 |
* This program is free software: you can redistribute it and/or modify |
|
6 |
* it under the terms of the GNU General Public License as published by |
|
7 |
* the Free Software Foundation, either version 3 of the License, or |
|
8 |
* (at your option) any later version. |
|
9 |
* |
|
10 |
* This program is distributed in the hope that it will be useful, |
|
11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 |
* GNU General Public License for more details. |
|
14 |
* |
|
15 |
* You should have received a copy of the GNU General Public License |
|
16 |
* along with this program. If not, see http://www.gnu.org/licenses/ |
|
17 |
*/ |
|
18 |
|
|
19 |
package de.uhilger.baselink; |
|
20 |
|
|
21 |
import java.io.ByteArrayInputStream; |
|
22 |
import java.lang.reflect.Method; |
|
23 |
import java.sql.Blob; |
|
24 |
import java.sql.Connection; |
|
25 |
import java.sql.PreparedStatement; |
|
26 |
import java.sql.Statement; |
|
27 |
import java.sql.ResultSet; |
|
28 |
import java.util.ArrayList; |
|
29 |
import java.util.Enumeration; |
|
30 |
import java.util.Hashtable; |
|
31 |
import java.util.List; |
|
32 |
import java.util.logging.Level; |
|
33 |
import java.util.logging.Logger; |
|
34 |
|
|
35 |
/** |
|
36 |
* Universal mapping of objects to and from a database |
|
37 |
* |
|
38 |
* Any class that has annotations for <code>DBTable</code> and |
|
39 |
* <code>DBColumns</code> can be persisted using <code>GenericRecord</code>. |
|
40 |
* |
|
41 |
* @author Copyright (c) Ulrich Hilger, http://uhilger.de |
|
42 |
* @author Published under the terms and conditions of |
|
43 |
* the <a href="http://www.gnu.org/licenses/" target="_blank">GNU General Public License</a> |
|
44 |
*/ |
|
45 |
public class GenericRecord implements Record { |
|
46 |
|
|
47 |
/** default getter method indicator */ |
|
48 |
public static final String GETTER_NAME = "get"; |
|
49 |
/** default setter method indicator */ |
|
50 |
public static final String SETTER_NAME = "set"; |
|
51 |
|
|
52 |
/** name of table this instance of GenericRecord references */ |
|
53 |
private String tableName; |
|
54 |
/** list of column names that make up the primary key */ |
|
55 |
private List<String> primaryKeyColNames; |
|
56 |
/** reference to field setters and getters */ |
|
57 |
private Hashtable<String,Field> columns; |
|
58 |
/** reference to class that this instance of GenericRecord maps to and from a database */ |
|
59 |
private Class<?> recordClass; |
|
60 |
|
|
61 |
/** |
|
62 |
* Create a new object of class GenericRecord |
|
63 |
* @param c the class to persist |
|
64 |
*/ |
|
65 |
public GenericRecord(Class<?> c) { |
|
66 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(c.getName()); |
|
67 |
this.recordClass = c; |
|
68 |
|
|
69 |
DBTable table = c.getAnnotation(DBTable.class); |
|
70 |
if(table != null) { |
|
71 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(tableName); |
|
72 |
tableName = table.name(); |
|
73 |
} else { |
|
74 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("missing table annotation"); |
|
75 |
} |
|
76 |
|
|
77 |
primaryKeyColNames = new ArrayList<String>(); |
|
78 |
DBPrimaryKey primaryKey = c.getAnnotation(DBPrimaryKey.class); |
|
79 |
if(primaryKey != null) { |
|
80 |
String[] names = primaryKey.value(); |
|
81 |
for(int i = 0; i < names.length; i++) { |
|
82 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(names[i]); |
|
83 |
primaryKeyColNames.add(names[i]); |
|
84 |
} |
|
85 |
} else { |
|
86 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("missing primary key annotation"); |
|
87 |
} |
|
88 |
|
|
89 |
columns = new Hashtable<String,Field>(); |
|
90 |
Method[] methods = c.getMethods(); |
|
91 |
for(int i = 0; i < methods.length; i++) { |
|
92 |
Method method = methods[i]; |
|
93 |
DBColumn field = method.getAnnotation(DBColumn.class); |
|
94 |
if(field != null) { |
|
95 |
Field mapper = new Field(); |
|
96 |
String fieldName = field.name(); |
|
97 |
mapper.setColumnType(field.type()); |
|
98 |
mapper.setColumnName(fieldName); |
|
99 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(fieldName); |
|
100 |
String methodName = method.getName(); |
|
101 |
if(methodName.startsWith(GETTER_NAME)) { |
|
102 |
String fieldMethod = methodName.substring(3); |
|
103 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(fieldMethod); |
|
104 |
Class<?> returnType = method.getReturnType(); |
|
105 |
try { |
|
106 |
mapper.setSetter(c.getMethod(SETTER_NAME + fieldMethod, returnType)); |
|
107 |
} catch (Exception e) { |
|
108 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.getLocalizedMessage(), e); |
|
109 |
} |
|
110 |
mapper.setGetter(method); |
|
111 |
} |
|
112 |
columns.put(fieldName, mapper); |
|
113 |
} |
|
114 |
} |
|
115 |
} |
|
116 |
|
|
117 |
/** |
|
118 |
* Get a statement suitable to delete a given object from the database |
|
119 |
* @param c the database connection to use for the delete |
|
120 |
* @param record the object to delete |
|
121 |
* @return the delete statement |
|
122 |
* @throws Exception |
|
123 |
*/ |
|
124 |
public PreparedStatement getDeleteStatment(Connection c, Object record) throws Exception { |
|
125 |
StringBuffer sql = new StringBuffer(); |
|
126 |
sql.append("delete from "); |
|
127 |
sql.append(tableName); |
|
128 |
sql.append(" where "); |
|
129 |
appendPrimaryKeyFields(sql); |
|
130 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(sql.toString()); |
|
131 |
PreparedStatement ps = c.prepareStatement(sql.toString()); |
|
132 |
prepareFields(0, primaryKeyColNames, ps, record); |
|
133 |
return ps; |
|
134 |
} |
|
135 |
|
|
136 |
/** |
|
137 |
* Get a statement suitable to insert a given object to the database |
|
138 |
* @param c the database connection to use for the insert |
|
139 |
* @param record the object to insert |
|
140 |
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be returned; |
|
141 |
* one of Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS |
|
142 |
* @return the insert statement |
|
143 |
* @throws Exception |
|
144 |
*/ |
|
145 |
public PreparedStatement getInsertStmt(Connection c, Object record, int autoGeneratedKeys) throws Exception { |
|
146 |
StringBuffer sql = new StringBuffer(); |
|
147 |
sql.append("insert into "); |
|
148 |
sql.append(tableName); |
|
149 |
sql.append("("); |
|
150 |
Enumeration<String> fieldNames = columns.keys(); |
|
151 |
StringBuffer fieldList = new StringBuffer(); |
|
152 |
StringBuffer valueList = new StringBuffer(); |
|
153 |
while(fieldNames.hasMoreElements()) { |
|
154 |
if(fieldList.length() > 0) { |
|
155 |
fieldList.append(","); |
|
156 |
valueList.append(","); |
|
157 |
} |
|
158 |
fieldList.append(fieldNames.nextElement()); |
|
159 |
valueList.append("?"); |
|
160 |
} |
|
161 |
sql.append(fieldList); |
|
162 |
sql.append(")"); |
|
163 |
sql.append(" values (");//?, ?)"); |
|
164 |
sql.append(valueList); |
|
165 |
sql.append(")"); |
|
166 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(sql.toString()); |
|
167 |
PreparedStatement ps = c.prepareStatement(sql.toString(), autoGeneratedKeys); |
|
168 |
prepareFields(0, ps, record); |
|
169 |
return ps; |
|
170 |
} |
|
171 |
|
|
172 |
/** |
|
173 |
* Get a statement suitable to insert a given object to the database |
|
174 |
* @param c the database connection to use for the insert |
|
175 |
* @param record the object to insert |
|
176 |
* @return the insert statement |
|
177 |
* @throws Exception |
|
178 |
*/ |
|
179 |
public PreparedStatement getInsertStatment(Connection c, Object record) throws Exception { |
|
180 |
return getInsertStmt(c, record, Statement.NO_GENERATED_KEYS); |
|
181 |
} |
|
182 |
|
|
183 |
/** |
|
184 |
* Get a statement suitable to update a given object in the database |
|
185 |
* @param c the database connection to use for the update |
|
186 |
* @param record the object to update |
|
187 |
* @return the update statement |
|
188 |
* @throws Exception |
|
189 |
*/ |
|
190 |
public PreparedStatement getUpdateStatment(Connection c, Object record) throws Exception { |
|
191 |
StringBuffer sql = new StringBuffer(); |
|
192 |
sql.append("update "); |
|
193 |
sql.append(tableName); |
|
194 |
sql.append(" set "); |
|
195 |
Enumeration<String> fieldNames = columns.keys(); |
|
196 |
StringBuffer fieldList = new StringBuffer(); |
|
197 |
while(fieldNames.hasMoreElements()) { |
|
198 |
if(fieldList.length() > 0) { |
|
199 |
fieldList.append(", "); |
|
200 |
} |
|
201 |
fieldList.append(fieldNames.nextElement()); |
|
202 |
fieldList.append("=?"); |
|
203 |
} |
|
204 |
sql.append(fieldList); |
|
205 |
sql.append(" where "); |
|
206 |
appendPrimaryKeyFields(sql); |
|
207 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(sql.toString()); |
|
208 |
PreparedStatement ps = c.prepareStatement(sql.toString()); |
|
209 |
prepareFields(0, ps, record); |
|
210 |
prepareFields(columns.size(), primaryKeyColNames, ps, record); |
|
211 |
return ps; |
|
212 |
} |
|
213 |
|
|
214 |
/** |
|
215 |
* Get contents of this record as an object |
|
216 |
* @param resultSet a resultSet that points to the record to get as an object |
|
217 |
* @param includeBlobs indicator whether or not to include BLOBs |
|
218 |
* @return an object having the data of this record |
|
219 |
* @throws Exception |
|
220 |
*/ |
|
221 |
public Object toObject(ResultSet resultSet, boolean includeBlobs) throws Exception { |
|
222 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(recordClass.getName()); |
|
223 |
Object o = recordClass.newInstance(); |
|
224 |
Enumeration<String> fieldNames = columns.keys(); |
|
225 |
while(fieldNames.hasMoreElements()) { |
|
226 |
String columnName = fieldNames.nextElement(); |
|
227 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(columnName); |
|
228 |
Field mapper = columns.get(columnName); |
|
229 |
Method setter = mapper.getSetter(); |
|
230 |
if(setter != null) { |
|
231 |
Object data = null; |
|
232 |
if(mapper.getColumnType().equals(DBColumn.Type.BLOB)) { |
|
233 |
if(includeBlobs) { |
|
234 |
Blob blob = resultSet.getBlob(columnName); |
|
235 |
data = new String(blob.getBytes((long) 1, (int) blob.length())); |
|
236 |
setter.invoke(o, data); |
|
237 |
} |
|
238 |
} else { |
|
239 |
data = resultSet.getObject(columnName); |
|
240 |
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(data.toString()); |
|
241 |
setter.invoke(o, data); |
|
242 |
} |
|
243 |
} |
|
244 |
} |
|
245 |
return o; |
|
246 |
} |
|
247 |
|
|
248 |
/** |
|
249 |
* append names of primary key columns to an SQL string |
|
250 |
* @param sql the SQL string to append to |
|
251 |
*/ |
|
252 |
private void appendPrimaryKeyFields(StringBuffer sql) { |
|
253 |
for(int i = 0; i < primaryKeyColNames.size(); i++) { |
|
254 |
if(i > 0) { |
|
255 |
sql.append(" and "); |
|
256 |
} |
|
257 |
sql.append(primaryKeyColNames.get(i)); |
|
258 |
sql.append("=?"); |
|
259 |
} |
|
260 |
} |
|
261 |
|
|
262 |
/** |
|
263 |
* Prepare fields of a PreparedStatement |
|
264 |
* @param offset number of field to start with |
|
265 |
* @param ps the statement to prepare |
|
266 |
* @param record object that has the data for the statement |
|
267 |
* @throws Exception |
|
268 |
*/ |
|
269 |
private void prepareFields(int offset, PreparedStatement ps, Object record) throws Exception { |
|
270 |
Enumeration<String> fieldNames = columns.keys(); |
|
271 |
int i = 1; |
|
272 |
while(fieldNames.hasMoreElements()) { |
|
273 |
Field mapper = columns.get(fieldNames.nextElement()); |
|
274 |
Method getter = mapper.getGetter(); |
|
275 |
Object o = getter.invoke(record, (Object[]) null); |
|
276 |
if(mapper.getColumnType().equals(DBColumn.Type.BLOB)) { |
|
277 |
String content = o.toString(); |
|
278 |
ps.setBinaryStream(i+offset, new ByteArrayInputStream(content.getBytes()), content.length()); |
|
279 |
} else { |
|
280 |
ps.setObject(i+offset, o); |
|
281 |
} |
|
282 |
i++; |
|
283 |
} |
|
284 |
} |
|
285 |
|
|
286 |
/** |
|
287 |
* Prepare fields of a PreparedStatement |
|
288 |
* @param offset number of field to start with |
|
289 |
* @param source list of field names |
|
290 |
* @param ps the statement to prepare |
|
291 |
* @param record object that has the data for the statement |
|
292 |
* @throws Exception |
|
293 |
*/ |
|
294 |
private void prepareFields(int offset, List<String> source, PreparedStatement ps, Object record) throws Exception { |
|
295 |
for(int i = 0; i < source.size(); i++) { |
|
296 |
Field mapper = columns.get(source.get(i)); |
|
297 |
Method getter = mapper.getGetter(); |
|
298 |
Object o = getter.invoke(record, (Object[]) null); |
|
299 |
ps.setObject(i+offset+1, o); |
|
300 |
} |
|
301 |
} |
|
302 |
|
|
303 |
/** |
|
304 |
* Get the name of the database table this record references |
|
305 |
* @return the table name |
|
306 |
*/ |
|
307 |
public String getTableName() { |
|
308 |
return tableName; |
|
309 |
} |
|
310 |
|
|
311 |
} |