ulrich
2020-05-26 07913e9b8a89cfa59984ace69fe39ed54b41606c
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 }