undisclosed
2023-10-19 b88616bc6e37ecba2404360426113f72f6c94b30
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 {
e1568a 46   private static final Logger logger = Logger.getLogger(GenericRecord.class.getName());
c387eb 47   
U 48     /** default getter method indicator */
49     public static final String GETTER_NAME = "get";
50     /** default setter method indicator */
51     public static final String SETTER_NAME = "set";
52     
53     /** name of table this instance of GenericRecord references */
54     private String tableName;
55     /** list of column names that make up the primary key */
56     private List<String> primaryKeyColNames;
57     /** reference to field setters and getters */
58     private Hashtable<String,Field> columns;
59     /** reference to class that this instance of GenericRecord maps to and from a database */
60     private Class<?> recordClass;
61
62     /**
63      * Create a new object of class GenericRecord
64      * @param c  the class to persist
65      */
66     public GenericRecord(Class<?> c) {        
e1568a 67         logger.finest(c.getName());
c387eb 68         this.recordClass = c;
U 69         
70         DBTable table = c.getAnnotation(DBTable.class);
71         if(table != null) {
e1568a 72             logger.finest(tableName);
c387eb 73             tableName = table.name();
U 74         } else {
e1568a 75             logger.severe("missing table annotation");
c387eb 76         }
U 77         
78         primaryKeyColNames = new ArrayList<String>();
79         DBPrimaryKey primaryKey = c.getAnnotation(DBPrimaryKey.class);
80         if(primaryKey != null) {
81             String[] names = primaryKey.value();
82             for(int i = 0; i < names.length; i++) {
e1568a 83                 logger.finest(names[i]);
c387eb 84                 primaryKeyColNames.add(names[i]);
U 85             }
86         } else {
e1568a 87             logger.severe("missing primary key annotation");
c387eb 88         }
U 89
90         columns = new Hashtable<String,Field>();
91         Method[] methods = c.getMethods();
92         for(int i = 0; i < methods.length; i++) {
93             Method method = methods[i];
94             DBColumn field = method.getAnnotation(DBColumn.class);
95             if(field != null) {
96                 Field mapper = new Field();
97                 String fieldName = field.name();
98                 mapper.setColumnType(field.type());
99                 mapper.setColumnName(fieldName);
e1568a 100                 logger.finest(fieldName);
c387eb 101                 String methodName = method.getName();
U 102                 if(methodName.startsWith(GETTER_NAME)) {
103                     String fieldMethod = methodName.substring(3);
e1568a 104                     logger.finest(fieldMethod);
c387eb 105                     Class<?> returnType = method.getReturnType();
U 106                     try {
107                         mapper.setSetter(c.getMethod(SETTER_NAME + fieldMethod, returnType));
108                     } catch (Exception e) {
e1568a 109                         logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
c387eb 110                     }
U 111                     mapper.setGetter(method);
112                 }
113                 columns.put(fieldName, mapper);
114             }
115         }
116     }
117     
118     /**
119      * Get a statement suitable to delete a given object from the database
120      * @param c  the database connection to use for the delete 
121      * @param record  the object to delete
122      * @return  the delete statement
123      * @throws Exception
124      */
125     public PreparedStatement getDeleteStatment(Connection c, Object record) throws Exception {
126         StringBuffer sql = new StringBuffer();
127         sql.append("delete from ");
128         sql.append(tableName);
129         sql.append(" where ");
130         appendPrimaryKeyFields(sql);
e1568a 131         logger.finest(sql.toString());
c387eb 132         PreparedStatement ps = c.prepareStatement(sql.toString());
U 133         prepareFields(0, primaryKeyColNames, ps, record);
134         return ps;
135     }
136
137   /**
138    * Get a statement suitable to insert a given object to the database
139      * @param c  the database connection to use for the insert 
140      * @param record  the object to insert
141    * @param autoGeneratedKeys  a flag indicating whether auto-generated keys should be returned; 
142    *          one of Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
143      * @return  the insert statement
144      * @throws Exception
145      */
146     public PreparedStatement getInsertStmt(Connection c, Object record, int autoGeneratedKeys) throws Exception {
147         StringBuffer sql = new StringBuffer();
148         sql.append("insert into ");
149         sql.append(tableName);
150         sql.append("(");
151         Enumeration<String> fieldNames = columns.keys();
152         StringBuffer fieldList = new StringBuffer();
153         StringBuffer valueList = new StringBuffer();
154         while(fieldNames.hasMoreElements()) {
155             if(fieldList.length() > 0) {
156                 fieldList.append(",");
157                 valueList.append(",");
158             }
159             fieldList.append(fieldNames.nextElement());
160             valueList.append("?");
161         }
162         sql.append(fieldList);
163         sql.append(")");
164         sql.append(" values (");//?, ?)");
165         sql.append(valueList);
166         sql.append(")");
e1568a 167         logger.finest(sql.toString());
c387eb 168         PreparedStatement ps = c.prepareStatement(sql.toString(), autoGeneratedKeys);
U 169         prepareFields(0, ps, record);
170         return ps;
171     }
172       
173     /**
174      * Get a statement suitable to insert a given object to the database
175      * @param c  the database connection to use for the insert 
176      * @param record  the object to insert
177      * @return  the insert statement
178      * @throws Exception
179      */
180     public PreparedStatement getInsertStatment(Connection c, Object record) throws Exception {
181         return getInsertStmt(c, record, Statement.NO_GENERATED_KEYS);
182     }
183   
184     /**
185      * Get a statement suitable to update a given object in the database
186      * @param c  the database connection to use for the update 
187      * @param record  the object to update
188      * @return  the update statement
189      * @throws Exception
190      */
191     public PreparedStatement getUpdateStatment(Connection c, Object record) throws Exception {
192         StringBuffer sql = new StringBuffer();
193         sql.append("update ");
194         sql.append(tableName);
195         sql.append(" set ");        
196         Enumeration<String> fieldNames = columns.keys();
197         StringBuffer fieldList = new StringBuffer();
198         while(fieldNames.hasMoreElements()) {
199             if(fieldList.length() > 0) {
200                 fieldList.append(", ");
201             }
202             fieldList.append(fieldNames.nextElement());
203             fieldList.append("=?");
204         }
205         sql.append(fieldList);
206         sql.append(" where ");
207         appendPrimaryKeyFields(sql);        
e1568a 208         logger.finest(sql.toString());
c387eb 209         PreparedStatement ps = c.prepareStatement(sql.toString());
U 210         prepareFields(0, ps, record);
211         prepareFields(columns.size(), primaryKeyColNames, ps, record);
212         return ps;
213     }
214
215     /**
216      * Get contents of this record as an object
217      * @param resultSet  a resultSet that points to the record to get as an object
218      * @param includeBlobs  indicator whether or not to include BLOBs 
219      * @return  an object having the data of this record 
220      * @throws Exception
221      */
222     public Object toObject(ResultSet resultSet, boolean includeBlobs) throws Exception {
e1568a 223         logger.finest(recordClass.getName());
c387eb 224         Object o = recordClass.newInstance();
U 225         Enumeration<String> fieldNames = columns.keys();
226         while(fieldNames.hasMoreElements()) {
227             String columnName = fieldNames.nextElement();
e1568a 228             logger.finest(columnName);
c387eb 229             Field mapper = columns.get(columnName);
U 230             Method setter = mapper.getSetter();
231             if(setter != null) {
232                 Object data = null;
233                 if(mapper.getColumnType().equals(DBColumn.Type.BLOB)) {
234                     if(includeBlobs) {
235                         Blob blob = resultSet.getBlob(columnName);
236                         data = new String(blob.getBytes((long) 1, (int) blob.length()));
237                         setter.invoke(o, data);
238                     }
239                 } else {
240                     data = resultSet.getObject(columnName);
e1568a 241                     logger.finest(data.toString());
c387eb 242                     setter.invoke(o, data);
U 243                 }
244             }
245         }
246         return o;
247     }
248      
249     /**
250      * append names of primary key columns to an SQL string
251      * @param sql  the SQL string to append to
252      */
253     private void appendPrimaryKeyFields(StringBuffer sql) {
254         for(int i = 0; i < primaryKeyColNames.size(); i++) {
255             if(i > 0) {
256                 sql.append(" and ");
257             }
258             sql.append(primaryKeyColNames.get(i));
259             sql.append("=?");
260         }
261     }
262     
263     /**
264      * Prepare fields of a PreparedStatement
265      * @param offset  number of field to start with
266      * @param ps  the statement to prepare
267      * @param record  object that has the data for the statement
268      * @throws Exception
269      */
270     private void prepareFields(int offset, PreparedStatement ps, Object record) throws Exception {
271         Enumeration<String> fieldNames = columns.keys();
272         int i = 1;
273         while(fieldNames.hasMoreElements()) {
274             Field mapper = columns.get(fieldNames.nextElement());
275             Method getter = mapper.getGetter(); 
276             Object o = getter.invoke(record, (Object[]) null);
277             if(mapper.getColumnType().equals(DBColumn.Type.BLOB)) {
278                 String content = o.toString();
279                 ps.setBinaryStream(i+offset, new ByteArrayInputStream(content.getBytes()), content.length());
280             } else {
281                 ps.setObject(i+offset, o);
e1568a 282         int nr = i + offset;
U 283         logger.finest(nr + " " + o.toString());
c387eb 284             }
U 285             i++;
286         }
287     }
288
289     /**
290      * Prepare fields of a PreparedStatement
291      * @param offset  number of field to start with
292      * @param source  list of field names 
293      * @param ps  the statement to prepare
294      * @param record  object that has the data for the statement
295      * @throws Exception
296      */
297     private void prepareFields(int offset, List<String> source, PreparedStatement ps, Object record) throws Exception {
298         for(int i = 0; i < source.size(); i++) {
299             Field mapper = columns.get(source.get(i));
300             Method getter = mapper.getGetter();
301             Object o = getter.invoke(record, (Object[]) null);
302             ps.setObject(i+offset+1, o);
303         }
304     }
305     
306     /**
307      * Get the name of the database table this record references
308      * @return  the table name
309      */
310     public String getTableName() {
311         return tableName;
312     }
313
314 }