1
2
3
4
5
6 package pt.digitalis.dif.model.dataset;
7
8 import java.beans.PropertyDescriptor;
9 import java.util.LinkedHashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Map.Entry;
13 import java.util.Set;
14
15 import org.apache.commons.beanutils.PropertyUtils;
16
17 import pt.digitalis.utils.common.IBeanAttributes;
18 import pt.digitalis.utils.common.StringUtils;
19 import pt.digitalis.utils.common.collections.CaseInsensitiveHashMap;
20 import pt.digitalis.utils.common.collections.CaseInsentiveArrayList;
21
22
23
24
25
26
27
28
29
30 public abstract class AbstractDataSet<T extends IBeanAttributes> implements IDataSet<T> {
31
32
33
34
35
36
37
38
39 static public void throwUnsuportedOperationException(String reason) throws DataSetException
40 {
41 StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
42
43 throw new DataSetException(reason, new RuntimeException("Unsuported Dataset Operation: "
44 + stackTraceElements[1].getClassName() + stackTraceElements[1].getMethodName() + "\n" + reason));
45 }
46
47
48 protected CaseInsensitiveHashMap<AttributeDefinition> attributesDefinition;
49
50
51 private LinkedHashMap<String, ChangeDescriptor<T>> changeSet = new LinkedHashMap<String, ChangeDescriptor<T>>();
52
53
54 protected Class<T> clazz;
55
56
57 protected String idAttribute = "id";
58
59
60 private IIDGenerator<T> idGenerator;
61
62
63 boolean ignoreDevelopmentErrors = false;
64
65
66 private boolean trackChanges = false;
67
68
69
70
71
72
73 public AbstractDataSet(Class<T> clazz)
74 {
75 this.clazz = clazz;
76 }
77
78
79
80
81 final public boolean delete(String id) throws DataSetException
82 {
83 if (this.isTrackChanges())
84 {
85 T instance = this.get(id);
86
87
88 if (instance != null)
89 {
90
91 ChangeDescriptor<T> change = this.getInternalChangeSet().get(id);
92
93 if (change == null)
94 {
95
96 change = new ChangeDescriptor<T>(DMLOperation.DELETE, RecordType.ORIGINAL, instance);
97 this.getInternalChangeSet().put(id, change);
98 }
99 else
100 {
101
102
103 change = new ChangeDescriptor<T>(DMLOperation.DELETE, change.getRecordType(), instance);
104 this.getInternalChangeSet().put(id, change);
105 }
106 }
107 }
108
109 return this.deleteSpecific(id);
110 }
111
112
113
114
115
116
117
118
119
120 abstract public boolean deleteSpecific(String id) throws DataSetException;
121
122
123
124
125 protected void detectAttributeDefinition()
126 {
127 if (clazz != null)
128 {
129 PropertyDescriptor[] propDescriptors = PropertyUtils.getPropertyDescriptors(clazz);
130 CaseInsensitiveHashMap<AttributeDefinition> tempAttributesDef = new CaseInsensitiveHashMap<AttributeDefinition>();
131
132 for (PropertyDescriptor propDescriptor: propDescriptors)
133 {
134
135 if (!propDescriptor.getName().equals("class") && !propDescriptor.getName().equals("bytes")
136 && (propDescriptor.getPropertyType() != Map.class)
137 && (propDescriptor.getPropertyType() != List.class)
138 && (propDescriptor.getPropertyType() != Set.class))
139 {
140 AttributeDefinition def = new AttributeDefinition(propDescriptor.getName(),
141 StringUtils.camelCaseToString(propDescriptor.getName()), propDescriptor.getPropertyType());
142
143 tempAttributesDef.put(propDescriptor.getName(), def);
144 }
145 }
146
147 attributesDefinition = tempAttributesDef;
148 }
149 }
150
151
152
153
154
155
156
157
158
159
160 @SuppressWarnings("unchecked")
161 public List<T> executeQuery(Query<T> query) throws DataSetException
162 {
163 CollectorListProcessor processor = new CollectorListProcessor();
164 this.executeQuery(query, processor);
165
166 return (List<T>) processor.getList();
167 }
168
169
170
171
172 public AttributeDefinition getAttributeDefinition(String attribute)
173 {
174 return this.getAttributesDefinition().get(attribute);
175 }
176
177
178
179
180 public CaseInsentiveArrayList getAttributeList()
181 {
182 return new CaseInsentiveArrayList(getAttributesDefinition().keySet());
183 }
184
185
186
187
188 public CaseInsensitiveHashMap<AttributeDefinition> getAttributesDefinition()
189 {
190
191 if (attributesDefinition == null)
192 detectAttributeDefinition();
193
194 return attributesDefinition;
195 }
196
197
198
199
200 public Map<String, ChangeDescriptor<T>> getChanges() throws DataSetException
201 {
202 LinkedHashMap<String, ChangeDescriptor<T>> result = new LinkedHashMap<String, ChangeDescriptor<T>>();
203
204 for (Entry<String, ChangeDescriptor<T>> change: this.getInternalChangeSet().entrySet())
205 {
206 ChangeDescriptor<T> internalChangeDescriptor = change.getValue();
207
208 if (internalChangeDescriptor.getOperation() == DMLOperation.DELETE
209 && internalChangeDescriptor.getRecordType() == RecordType.NEW)
210
211
212 ;
213 else
214 {
215 T bean = internalChangeDescriptor.getBeanInstance();
216 if (bean == null)
217 bean = this.get(change.getKey());
218
219 ChangeDescriptor<T> newChangeDescriptor = new ChangeDescriptor<T>(
220 internalChangeDescriptor.getOperation(), internalChangeDescriptor.getRecordType(), bean);
221
222 result.put(change.getKey(), newChangeDescriptor);
223 }
224 }
225
226 return result;
227 }
228
229
230
231
232 public String getIDFieldName()
233 {
234 return idAttribute;
235 }
236
237
238
239
240
241
242 public IIDGenerator<T> getIdGenerator()
243 {
244 return idGenerator;
245 }
246
247
248
249
250
251
252 protected Map<String, ChangeDescriptor<T>> getInternalChangeSet()
253 {
254 return changeSet;
255 }
256
257
258
259
260 public T insert(String id, Map<String, String> attributeValues) throws DataSetException
261 {
262
263 T instance = instanciateDataObject(attributeValues);
264 if (id != null)
265 {
266 instance.setAttributeFromString(idAttribute, id);
267 }
268
269 return insert(instance);
270 }
271
272
273
274
275 final public T insert(T instance) throws DataSetException
276 {
277
278 if (this.getIdGenerator() != null && instance.getAttribute(this.getIDFieldName()) == null)
279 {
280 String newID = this.getIdGenerator().generateID(instance);
281 instance.setAttributeFromString(this.getIDFieldName(), newID);
282 }
283
284 instance = this.insertSpecific(instance);
285
286 if (this.getIdGenerator() != null)
287 this.getIdGenerator().reportInsertedID(instance.getAttributeAsString(this.getIDFieldName()));
288
289
290 if (this.isTrackChanges())
291 {
292 if (instance.getAttribute(this.getIDFieldName()) == null)
293 throwUnsuportedOperationException("Cannot use track changes for empty ID inserts. "
294 + "Set the ID field before inserting the record in the dataset.");
295 else
296 {
297 ChangeDescriptor<T> change = this.getInternalChangeSet().get(
298 instance.getAttributeAsString(this.getIDFieldName()));
299
300 if (change == null)
301 {
302
303 change = new ChangeDescriptor<T>(DMLOperation.INSERT, RecordType.NEW, null);
304 this.getInternalChangeSet().put(instance.getAttributeAsString(this.getIDFieldName()), change);
305 }
306 else
307 {
308
309 if (change.getRecordType() == RecordType.NEW)
310
311 change = new ChangeDescriptor<T>(DMLOperation.INSERT, RecordType.NEW, null);
312 else
313 {
314
315
316 change = new ChangeDescriptor<T>(DMLOperation.UPDATE, RecordType.UPDATED, null);
317 }
318
319 this.getInternalChangeSet().put(instance.getAttributeAsString(this.getIDFieldName()), change);
320 }
321 }
322 }
323
324 return instance;
325 }
326
327
328
329
330
331
332
333
334
335 abstract public T insertSpecific(T instance) throws DataSetException;
336
337
338
339
340 protected T instanciateDataObject()
341 {
342 if (clazz != null)
343 {
344 try
345 {
346 T instance = clazz.newInstance();
347
348 return instance;
349 }
350 catch (InstantiationException e)
351 {
352 return null;
353 }
354 catch (IllegalAccessException e)
355 {
356 return null;
357 }
358 }
359 return null;
360 }
361
362
363
364
365
366
367 protected T instanciateDataObject(Map<String, String> attributeValues)
368 {
369 T instance = instanciateDataObject();
370
371 if (instance != null)
372 for (Entry<String, String> entry: attributeValues.entrySet())
373 instance.setAttributeFromString(entry.getKey(), entry.getValue());
374
375 return instance;
376 }
377
378
379
380
381 public boolean isCompositeID()
382 {
383 return false;
384 }
385
386
387
388
389 public boolean isIgnoreDevelopmentErrors()
390 {
391 return ignoreDevelopmentErrors;
392 }
393
394
395
396
397 public boolean isTrackChanges()
398 {
399 return trackChanges;
400 }
401
402
403
404
405 public T newDataInstance()
406 {
407 return instanciateDataObject();
408 }
409
410
411
412
413 public Query<T> query()
414 {
415 return new Query<T>(this, true, true);
416 }
417
418
419
420
421 public T refresh(T instance) throws DataSetException
422 {
423 return instance;
424 }
425
426
427
428
429 public void resetTrackChanges()
430 {
431 this.getInternalChangeSet().clear();
432 }
433
434
435
436
437
438
439
440
441 public void setIdGenerator(IIDGenerator<T> idGenerator) throws DataSetException
442 {
443 if (this.isCompositeID())
444 throwUnsuportedOperationException("Can only use ID Generator for non-composite ID datasets");
445 else
446 this.idGenerator = idGenerator;
447 }
448
449
450
451
452 public void setIgnoreDevelopmentErrors(boolean ignoreDevelopmentErrors)
453 {
454 this.ignoreDevelopmentErrors = ignoreDevelopmentErrors;
455 }
456
457
458
459
460 public void setTrackChanges(boolean trackChanges)
461 {
462 this.trackChanges = trackChanges;
463
464 if (trackChanges)
465 changeSet = new LinkedHashMap<String, ChangeDescriptor<T>>();
466 }
467
468
469
470
471 public T undelete(String id) throws DataSetException
472 {
473 if (!this.isTrackChanges())
474 {
475 throwUnsuportedOperationException("Cannot undelete a record without track changes active");
476
477
478 return null;
479 }
480 else
481 {
482 ChangeDescriptor<T> change = this.getInternalChangeSet().get(id);
483
484 if (change == null)
485 {
486 String messageRecordId = this.clazz.getSimpleName() + "[" + id + "]";
487 throw new DataSetException("Record not found for recover: " + messageRecordId);
488 }
489 else
490 {
491
492 T instance = this.insertSpecific(change.getBeanInstance());
493
494 if (change.getRecordType() == RecordType.NEW)
495 {
496
497 change = new ChangeDescriptor<T>(DMLOperation.INSERT, RecordType.NEW, null);
498 }
499 else if (change.getRecordType() == RecordType.UPDATED)
500 {
501
502 change = new ChangeDescriptor<T>(DMLOperation.UPDATE, RecordType.UPDATED, null);
503 }
504 else
505 {
506
507
508 change = null;
509 }
510
511 if (change == null)
512 this.getInternalChangeSet().remove(id);
513 else
514 this.getInternalChangeSet().put(id, change);
515
516 return instance;
517 }
518 }
519 }
520
521
522
523
524 public T update(String id, Map<String, String> attributeValues) throws DataSetException
525 {
526
527 T instance = get(id);
528
529 if (instance != null)
530 {
531 for (Entry<String, String> prop: attributeValues.entrySet())
532 {
533
534 if (prop.getValue() == null)
535 instance.setAttribute(prop.getKey(), null);
536 else if (!(instance.getAttribute(prop.getKey()) instanceof String) && "".equals(prop.getValue()))
537 instance.setAttribute(prop.getKey(), null);
538 else
539 instance.setAttributeFromString(prop.getKey(), prop.getValue());
540 }
541 return update(instance);
542 }
543 else
544 return null;
545 }
546
547
548
549
550 final public T update(T instance) throws DataSetException
551 {
552 instance = this.updateSpecific(instance);
553
554 if (this.isTrackChanges() && this.get(instance.getAttributeAsString(this.getIDFieldName())) != null)
555 {
556
557
558 String id = instance.getAttributeAsString(this.getIDFieldName());
559 ChangeDescriptor<T> change = this.getInternalChangeSet().get(id);
560
561 if (change == null)
562 {
563
564 change = new ChangeDescriptor<T>(DMLOperation.UPDATE, RecordType.UPDATED, null);
565 this.getInternalChangeSet().put(id, change);
566 }
567 else
568 {
569
570
571 if (change.getRecordType() == RecordType.NEW)
572 change = new ChangeDescriptor<T>(DMLOperation.INSERT, RecordType.NEW, null);
573 else
574 change = new ChangeDescriptor<T>(DMLOperation.UPDATE, RecordType.UPDATED, null);
575
576 this.getInternalChangeSet().put(id, change);
577 }
578 }
579
580 return instance;
581 }
582
583
584
585
586 public T updateBean(T instance) throws DataSetException
587 {
588 return update(instance);
589 }
590
591
592
593
594
595
596
597
598
599 abstract public T updateSpecific(T instance) throws DataSetException;
600
601 }