Source code: nectar/record/Record.java
1 /*
2 Copyright (C) 2003 Kai Schutte
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18 * Record.java
19 *
20 * Created on March 7, 2003, 6:54 PM
21 */
22
23 package nectar.record;
24
25 import nectar.record.datatypes.*;
26 import java.lang.reflect.Field;
27 import java.util.Map;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Vector;
31 import java.util.Collection;
32 import java.util.Iterator;
33 import java.util.Date;
34 import java.util.Calendar;
35
36 import java.io.Serializable;
37 import java.beans.PropertyChangeSupport;
38 import java.beans.PropertyChangeListener;
39 import java.beans.PropertyChangeEvent;
40
41 import nectar.view.RecordView;
42
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45
46
47 /** The abstract base model element for data records.<p>
48 *
49 * To retrieve records from the datasource, you must first know their ID numbers.
50 * To do this, see {@link nectar.data.DataAdapterService} on how to build and
51 * execute queries that will return relevant ID numbers.
52 * Once you have one or more ID number, you should retrieve records
53 * through the {@link nectar.services.RecordService} methods which return record instances.<p>
54 *
55 *
56 * You can then use all the <code>Record.get[*]()</code> methods on the record to retrieve raw data objects from the record,
57 * or retrieve a {@link nectar.view.RecordView} through the <code>Record.getView()</code> method.<p>
58 *
59 *
60 * Use the set*() methods to modify the data in a Record instance, followed by a {@link nectar.services.RecordService#commit} call to persist your changes to the datasource.<p>
61 *
62 * To create a new record, call the <code>Record.setupCreate()</code> method on a newly instantiated Record, use the <code>Record.set*()</code> to fill the data, and finally commit it to the database. A newly created record will only have an ID number once it has been committed.
63 *
64 *
65 * @author Kai Schutte skander@skander.com
66 * @see nectar.data.DataAdapterService
67 * @see nectar.services.RecordService
68 * @see nectar.view.RecordView
69 */
70 public abstract class Record implements Serializable {
71 private HashMap propertyChangeMap = new HashMap();
72 private static Log log = LogFactory.getLog(Record.class);
73 /** The Collection of tables that this record can build itself from.
74 */
75 protected Vector dataTables = new Vector();
76
77 /** The name of the top-level record table in the datasource.
78 */
79 public static String TABLE = "record";
80
81 /** Field name of the id field in the datasource.
82 */
83 public static final String FIELD_RECORD_ID = "r_id";
84 /** Field name of the project ID field in the datasource.
85 */
86 public static final String FIELD_PROJECT = "r_project";
87 /** Field name of the implementation id field in the datasource.
88 */
89 public static final String FIELD_CHILD_ID = "id";
90 /** Field name of the owner field in the datasource.
91 */
92 public static final String FIELD_OWNER = "r_owner";
93 /** Field name of the type field in the datasource.
94 */
95 public static final String FIELD_TYPE = "r_type";
96 /** Field name of the createdBy field in the datasource.
97 */
98 public static final String FIELD_CREATED_DATE = "r_created_date";
99 /** Field name of the modifiedDate field in the datasource.
100 */
101 public static final String FIELD_MODIFIED_DATE = "r_modified_date";
102 /** Field name of the createdDate field in the datasource.
103 */
104 public static final String FIELD_CREATED_BY = "r_created_by";
105 /** Field name of the status field in the datasource.
106 */
107 public static final String FIELD_STATUS = "r_status";
108 /** Field name of the order_by field in the datasource.
109 */
110 public static final String FIELD_ORDER_BY = "r_order_by";
111
112
113 /** Array of field names defined for all records.
114 */
115 public static final String[] TOP_LEVEL_FIELD_NAMES = {FIELD_RECORD_ID,FIELD_PROJECT,FIELD_OWNER,FIELD_TYPE,FIELD_CREATED_DATE,FIELD_MODIFIED_DATE,FIELD_CREATED_BY,FIELD_STATUS, FIELD_ORDER_BY};
116
117 private RecordInteger id;
118 private RecordInteger project;
119 private RecordInteger owner;
120 private RecordTypeEnum type;
121 private RecordDate createdDate;
122 private RecordDate modifiedDate;
123 private RecordInteger createdBy;
124 private RecordStatusEnum status;
125 private RecordInteger orderBy;
126 protected Vector fields;
127
128 /** Creates a new, empty instance of a Record model. To load records, you should use <CODE>RecordService.get[Record](id);</CODE> instead of this constructor. To create a new Record to be inserted into the datasource, you should use the constructors of the child class of the Record class, then call <code>setupCreate()</code>
129 * @see nectar.services.RecordService
130 */
131 protected Record() {
132 dataTables.add(Record.TABLE);
133 id = new RecordInteger(FIELD_RECORD_ID, false);
134 project = new RecordInteger(FIELD_PROJECT, true);
135 owner = new RecordInteger(FIELD_OWNER, true);
136 type = new RecordTypeEnum(FIELD_TYPE);
137 createdDate = new RecordDate(FIELD_CREATED_DATE, false);
138 modifiedDate = new RecordDate(FIELD_MODIFIED_DATE, false);
139 createdBy = new RecordInteger(FIELD_CREATED_BY, false);
140 status = new RecordStatusEnum(FIELD_STATUS);
141 orderBy = new RecordInteger(FIELD_ORDER_BY, false);
142 fields = new Vector(20);
143 fields.add(id);
144 fields.add(project);
145 fields.add(owner);
146 fields.add(type);
147 fields.add(createdDate);
148 fields.add(modifiedDate);
149 fields.add(createdBy);
150 fields.add(status);
151 fields.add(orderBy);
152 }
153
154 public final Record copy() {
155 Record newRecord;
156 try {
157 newRecord = (Record)this.getClass().newInstance();
158 } catch (Exception e) { // shouldn't occur
159 e.printStackTrace();
160 return null;
161 }
162 newRecord.load(this.toObjectMap());
163 return newRecord;
164 }
165
166 /** Returns the type String for this Record.
167 * @return The type String for this Record.
168 */
169 public abstract String getRecordType();
170
171 /** Returns a Collection of Strings that lists the datasource names of the tables required to build this Record.
172 * @return The list of table names.
173 */
174 public final Collection getTables() {
175 return dataTables;
176 }
177
178 protected Collection getFields() {
179 Class recordClass = this.getClass();
180 Vector fields = new Vector();
181 while (recordClass != null) {
182 Field[] classFields = recordClass.getDeclaredFields();
183 for (int i=0; i<classFields.length; i++) {
184 Class fieldClass = classFields[i].getType();
185 Class fieldDeclaringClass = fieldClass.getSuperclass();
186 if (fieldDeclaringClass != null) {
187 try {
188 if (fieldDeclaringClass.getName() == "nectar.record.datatypes.RecordDataElement") {
189 fields.add(classFields[i].get(this));
190 }
191 } catch (IllegalAccessException e) {
192 if (log.isErrorEnabled()) {
193 log.error("While gathering field values in getFields(), field: \""+classFields[i].getName()+"\" was inaccessible. Ensure the field is protected, not private.", e);
194 }
195 }
196 }
197 }
198 recordClass = recordClass.getSuperclass();
199 }
200 return fields;
201 }
202
203 /** Returns the list of field names required to build this record.
204 * @return The list of field names required to build this record.
205 */
206 public final Collection getFieldNames() {
207 Collection fields = getFields();
208 Vector fieldNames = new Vector();
209 for (Iterator iter = fields.iterator(); iter.hasNext(); ) {
210 fieldNames.add(((RecordDataElement)iter.next()).getFieldName());
211 }
212 return fieldNames;
213 }
214
215
216 /** Loads the required data from the datasource into this record instance. To load a record you should use the <CODE>RecordService.getRecord(Long id)</CODE> method. Using this method directly will harm performance and is very insecure, since all the caching, distributed synchronization, etc. will be bypassed.
217 * @param values The [field_name] => [(Object) value] map to load into this record.
218 */
219 public final void load(Map values) {
220 Collection fields = this.getFields();
221 for (Iterator iter = fields.iterator(); iter.hasNext(); ) {
222 RecordDataElement field = (RecordDataElement)iter.next();
223 Object dataObj = values.get(field.getFieldName());
224 if (field == this.id && dataObj == null) continue;
225 try {
226 field.assignDataValue(dataObj);
227 } catch (ClassCastException e) {
228 throw new ClassCastException("NECTAR: Record Programming error: on loading record of type "+this.getClass().getName()+" - field: " +field.getFieldName()+" ClassCastException occured with message: "+e.getMessage());
229 }
230 }
231 }
232
233 /** Returns a [field_name] => [(String) value] HashMap of this record.
234 * @return the generated map.
235 */
236 public final java.util.HashMap toMap() {
237 Collection fields = getFields();
238 java.util.HashMap map = new java.util.HashMap();
239 for (Iterator iter=fields.iterator(); iter.hasNext(); ) {
240 RecordDataElement field = (RecordDataElement)iter.next();
241 map.put(field.getFieldName(), field.getStringValue());
242 }
243 return map;
244 }
245
246 public final java.util.HashMap toObjectMap() {
247 Collection fields = this.getFields();
248 java.util.HashMap map = new java.util.HashMap();
249 for (Iterator iter=fields.iterator(); iter.hasNext(); ) {
250 RecordDataElement field = (RecordDataElement)iter.next();
251 map.put(field.getFieldName(), field.getDataObject());
252 }
253 return map;
254 }
255
256 /** loads all the default and system values into an newly instantiated, empty Record instance.
257 * @param creator The ID number of the PersonRecord creating this record. The System's creator ID is 0.
258 */
259 protected void setupCreate(Long project, Long creator) {
260 this.project.assignDataValue(project);
261 type.assignDataValue(this.getRecordType());
262 createdDate.assignDataValue(Calendar.getInstance().getTime());
263 modifiedDate.assignDataValue(Calendar.getInstance().getTime());
264 createdBy.assignDataValue(creator);
265 status.assignDataValue("active");
266 orderBy.assignDataValue(new Long(0));
267 }
268
269 /** Returns the ID number of this record.
270 * @return The ID number of this record or null if the recorded hasn't been loaded.
271 */
272 public final Long getId() {
273 return id.getDataValue();
274 }
275
276 /** Returns the project ID number of this record.
277 * @return The ID number of the project that this record is linked to.
278 */
279 public final Long getProject() {
280 return project.getDataValue();
281 }
282
283
284 /** Sets the project ID for this record.
285 * @param value The project ID number of this record.
286 * @throws RecordInvalidInputException thrown if given id is null.
287 */
288 public final void setProject(Long value) throws RecordInvalidInputException {
289 if (value == null) throw new RecordInvalidInputException();
290 this.project.validate(value);
291 }
292
293
294 /** Sets the ID for this record. Note that you can't modify the id number of a record, only set it on a newly created instance.
295 * @param value The ID number of this record.
296 * @throws RecordInvalidInputException thrown when the ID number is already set for this record.
297 */
298 public final void setId(Long value) throws RecordInvalidInputException {
299 if (this.getId() == null)
300 this.id.assignDataValue(value);
301 else
302 throw new RecordInvalidInputException("ID already set!");
303 }
304
305 /** Returns the ID number of the record that owns this record or null if this record isn't owned by any other record.
306 * @return The ID number of the record that owns this record, may be null.
307 */
308 public final Long getOwner() {
309 return owner.getDataValue();
310 }
311
312 /** Sets the owner ID number of this record.
313 * @param value The ID number of the record that owns this record, may be null.
314 * @throws RecordInvalidInputException thrown if the owner record referred to with the given ID number does not exist, or isn't allowed to be the owner of this Record according to the record structure rules.
315 */
316 public final void setOwner(Long value) throws RecordInvalidInputException {
317 owner.validate(value);
318 }
319
320 /** Returns the Date at which this record was originally created.
321 * @return The Date at which this record was originally created.
322 * @see nectar.record.Record#setCreatedDate
323 */
324 public final Date getCreatedDate() {
325 return createdDate.getDataValue();
326 }
327
328 /** Sets the Date at which this record was originally created. This method always throws a {@link nectar.record.RecordInvalidInputException} since the createdDate is generated internally, and cannot be modified.
329 * @param value The Date at which this record was originally created.
330 * @throws RecordInvalidInputException always thrown since the createdDate is generated internally, and cannot be modified.
331 * @see nectar.record.Record#getCreatedDate
332 */
333 public final void setCreatedDate(Date value) throws RecordInvalidInputException {
334 createdDate.validate(value);
335 }
336
337 /** Returns the Date at which this record was last modified.
338 * @return The Date at which this record was last modified.
339 * @see nectar.record.Record#setModifiedDate
340 */
341 public final Date getModifiedDate() {
342 return modifiedDate.getDataValue();
343 }
344
345 /** Sets the Date at which this record was last modified. This method always throws a {@link nectar.record.RecordInvalidInputException} since the modifiedDate is generated internally, and cannot be modified.
346 * @param value The Date at which this record was last modified.
347 * @throws RecordInvalidInputException always thrown since the modifiedDate is generated internally, and cannot be modified.
348 * @see nectar.record.Record#setModifiedDate
349 */
350 public final void setModifiedDate(Date value) throws RecordInvalidInputException {
351 modifiedDate.validate(value);
352 }
353
354 /** Returns the ID number of the PersonRecord, which describes the person that created this record.
355 * @return The ID number of the PersonRecord, which describes the person that created this record.
356 * @see nectar.record.Record#setCreatedBy
357 * @see nectar.record.PersonRecord
358 */
359 public final Long getCreatedBy() {
360 return createdBy.getDataValue();
361 }
362
363 /** Sets the ID number of the PersonRecord, which describes the person that created this record.
364 * This method always throws a {@link nectar.record.RecordInvalidInputException} since the createdBy field is generated internally, and cannot be modified.
365 * @param value The ID number of the PersonRecord, which describes the person that created this record.
366 * @throws RecordInvalidInputException always thrown since the createdBy field is generated internally, and cannot be modified.
367 * @see nectar.record.Record#getCreatedBy
368 * @see nectar.record.PersonRecord
369 */
370 public final void setCreatedBy(Long value) throws RecordInvalidInputException {
371 createdBy.validate(value);
372 }
373
374 /** Returns the status of this record.
375 * @return The status of this record.
376 */
377 public final String getStatus() {
378 return status.getDataValue();
379 }
380
381 /** Sets the status of this record.
382 * @param value The status of this record.
383 * @throws RecordInvalidInputException if the given status String is not valid.
384 */
385 public final void setStatus(String value) throws RecordInvalidInputException {
386 status.validate(value);
387 }
388
389 /** Returns the ordering value of this record.
390 * @return The ordering value of this record.
391 */
392 public final Long getOrderBy() {
393 return orderBy.getDataValue();
394 }
395 /** Sets the ordering value of this record.
396 * @param value The ordering value of this record.
397 * @throws RecordInvalidInputException never thrown.
398 */
399 public final void setOrderBy(Long value) throws RecordInvalidInputException {
400 orderBy.validate(value);
401 }
402
403 /** Creates and returns appropriate RecordView instance for this record. You can safely typecast the return value to the RecordView subclass associated to this record.
404 * @return the RecordView associated to this Record.
405 */
406 public abstract nectar.view.RecordView getView();
407 }