001: package com.bm.utils;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.HashMap;
006: import java.util.HashSet;
007: import java.util.List;
008: import java.util.Map;
009: import java.util.Set;
010:
011: import javax.persistence.EntityManager;
012: import javax.persistence.EntityTransaction;
013: import javax.persistence.Query;
014:
015: import org.apache.log4j.Logger;
016:
017: import com.bm.introspectors.EmbeddedClassIntrospector;
018: import com.bm.introspectors.EntityBeanIntrospector;
019: import com.bm.introspectors.Property;
020: import com.bm.introspectors.relations.EntityReleationInfo;
021: import com.bm.introspectors.relations.ManyToOneReleation;
022: import com.bm.introspectors.relations.OneToManyReleation;
023: import com.bm.introspectors.relations.RelationType;
024:
025: /**
026: * This class generates a undo script for all DB oprations excuted in jUnit.
027: * tests
028: *
029: * @author Daniel Wiese
030: * @param <T> -
031: * the type of the entity bean
032: * @since 07.10.2005
033: */
034: public class UndoScriptGenerator<T> {
035:
036: private static final Logger log = Logger
037: .getLogger(UndoScriptGenerator.class);
038:
039: private final Set<T> createdObjects = new HashSet<T>();
040:
041: private final List<String> createdSQLScripts = new ArrayList<String>();
042:
043: private final Set<String> usedPersistentClasses = new HashSet<String>();
044:
045: private final EntityBeanIntrospector<T> inspector;
046:
047: private UndoScriptGenerator rootGenerator = null;
048:
049: /**
050: * if a bean has releated bens, a undo generatro for each releation is
051: * constructed
052: */
053: private final Map<Class, UndoScriptGenerator> subUndoGens = new HashMap<Class, UndoScriptGenerator>();
054: /**
055: * if a bean has releated bens, a undo generatro for each releation is
056: * constructed
057: */
058: private final Map<Class, DeleteOrder> subUndoGensOrder = new HashMap<Class, DeleteOrder>();
059:
060: /**
061: * Default constructor.
062: *
063: * @param inspector -
064: * the inspector
065: */
066: public UndoScriptGenerator(EntityBeanIntrospector<T> inspector) {
067: this (inspector, null);
068: }
069:
070: /**
071: * Default constructor.
072: *
073: * @param inspector -
074: * the inspector
075: * @param root -
076: * the root generator
077: */
078: private UndoScriptGenerator(EntityBeanIntrospector<T> inspector,
079: UndoScriptGenerator root) {
080: this .inspector = inspector;
081: this .rootGenerator = root;
082: }
083:
084: /**
085: * Returns the createdObjects.
086: *
087: * @return Returns the createdObjects.
088: */
089: public List<Object> getCreatedObjects() {
090: final List<Object> back = new ArrayList<Object>();
091: addSubDeleteObjects(back, DeleteOrder.DELETE_FIRST);
092: back.addAll(this .createdObjects);
093: addSubDeleteObjects(back, DeleteOrder.DELETE_AFTER);
094:
095: return back;
096: }
097:
098: /**
099: * This method returns a delete all statement for the tables (bean types).
100: *
101: * @return delete all statement
102: */
103: public List<String> getOneDeleteAllStatement() {
104: final List<String> back = new ArrayList<String>();
105: addSubDeleteAllScripts(back, DeleteOrder.DELETE_FIRST);
106: for (String currentClass : this .usedPersistentClasses) {
107: back.add("delete from " + currentClass);
108: }
109: addSubDeleteAllScripts(back, DeleteOrder.DELETE_AFTER);
110: return back;
111:
112: }
113:
114: /**
115: * Deteltes all data in all used tables.
116: *
117: * @param em
118: * the entity manager
119: */
120: public void deleteAllDataInAllUsedTables(EntityManager em) {
121: EntityTransaction tx = em.getTransaction();
122: tx.begin();
123: List<String> oneDeleteAllStatement = this
124: .getOneDeleteAllStatement();
125: for (String currentdelete : oneDeleteAllStatement) {
126: Query query = em.createQuery(currentdelete);
127: query.executeUpdate();
128: }
129: tx.commit();
130: }
131:
132: /**
133: * Returns a list of SQL undo statements.
134: *
135: * @return SQL statements
136: */
137: public List<String> getSQLUndoStatements() {
138: final List<String> back = new ArrayList<String>();
139: // rekursive solution
140: addSubDeleteScripts(back, DeleteOrder.DELETE_FIRST);
141: back.addAll(this .createdSQLScripts);
142: addSubDeleteScripts(back, DeleteOrder.DELETE_AFTER);
143:
144: return back;
145: }
146:
147: /**
148: * This method protocoll a bean creation.
149: *
150: * @param toCreate -
151: * the ben wich sould be protocolled as created
152: */
153: public void protokollCreate(T toCreate) {
154: final StringBuilder sb = new StringBuilder();
155: this .createdObjects.add(toCreate);
156: sb.append("DELETE FROM ").append(this .inspector.getTableName())
157: .append(" WHERE ");
158: sb.append(this .getPkCondition(toCreate));
159: this .createdSQLScripts.add(sb.toString());
160: this .usedPersistentClasses.add(toCreate.getClass().getName());
161:
162: // now iterate over persistent field and search for
163: // 1:N, N:1 or 1:! releations
164: List<Property> fields = this .inspector.getPersitentProperties();
165: for (Property akt : fields) {
166: EntityReleationInfo eri = this .inspector
167: .getPresistentFieldInfo(akt)
168: .getEntityReleationInfo();
169: // don't store unidirectional relations
170: if (eri != null && this .isRootGenerator()) {
171: this .processReletatedObjects(eri, toCreate);
172: }
173: }
174:
175: }
176:
177: /**
178: * The toString method- returns the undo-script.
179: *
180: * @return - list of undo statements
181: * @see java.lang.Object#toString()
182: */
183: @Override
184: public String toString() {
185: StringBuilder sb = new StringBuilder();
186: for (String akt : this .createdSQLScripts) {
187: sb.append(akt).append("\n");
188: }
189:
190: return sb.toString();
191: }
192:
193: private void addSubDeleteObjects(final List<Object> back,
194: DeleteOrder deleteOrder) {
195: final Set<Class> values = this .subUndoGens.keySet();
196: for (Class current : values) {
197: if (this .subUndoGensOrder.get(current).equals(deleteOrder)) {
198: UndoScriptGenerator<?> akt = this .subUndoGens
199: .get(current);
200: back.addAll(akt.getCreatedObjects());
201: }
202: }
203: }
204:
205: private void addSubDeleteScripts(final List<String> back,
206: DeleteOrder deleteOrder) {
207: final Set<Class> values = this .subUndoGens.keySet();
208: for (Class current : values) {
209: if (this .subUndoGensOrder.get(current).equals(deleteOrder)) {
210: UndoScriptGenerator<?> akt = this .subUndoGens
211: .get(current);
212: back.addAll(akt.getSQLUndoStatements());
213: }
214: }
215: }
216:
217: private void addSubDeleteAllScripts(final List<String> back,
218: DeleteOrder deleteOrder) {
219: final Set<Class> values = this .subUndoGens.keySet();
220: for (Class current : values) {
221: if (this .subUndoGensOrder.get(current).equals(deleteOrder)) {
222: UndoScriptGenerator<?> akt = this .subUndoGens
223: .get(current);
224: for (String currentClass : akt.usedPersistentClasses) {
225: back.add("delete from " + currentClass);
226: }
227: }
228: }
229: }
230:
231: /**
232: * Returns a value of an field
233: *
234: * @param instance -
235: * the instance
236: * @param toGet -
237: * the field to read the value
238: * @return - the readed value
239: * @throws IllegalAccessException
240: */
241: private Object getField(Object instance, Property toGet)
242: throws IllegalAccessException {
243: return toGet.getField(instance);
244: }
245:
246: /**
247: * Return a registered undo generator for a class or returns a registered
248: * one
249: *
250: * @param forClass
251: * @return - a undo generator for a type
252: */
253: @SuppressWarnings("unchecked")
254: private UndoScriptGenerator getInnerUndoScriptGen(Class forClass,
255: DeleteOrder deleteOrder) {
256: if (this .subUndoGens.containsKey(forClass)) {
257: return this .subUndoGens.get(forClass);
258: }
259: final EntityBeanIntrospector targetIntro = new EntityBeanIntrospector(
260: forClass);
261: final UndoScriptGenerator innerUndo = new UndoScriptGenerator(
262: targetIntro, this );
263: this .subUndoGens.put(forClass, innerUndo);
264: this .subUndoGensOrder.put(forClass, deleteOrder);
265: return innerUndo;
266:
267: }
268:
269: /**
270: * Generated the pk where condition for the entity bean
271: *
272: * @param toCreate -
273: * the entity bean with a pk
274: * @return - the where clause with pk
275: */
276: private String getPkCondition(T toCreate) {
277: String aktFieldName = "";
278: try {
279: final StringBuilder sb = new StringBuilder();
280: List<Property> pkProperties = null;
281:
282: // check if is embedded or not
283: if (this .inspector.hasEmbeddedPKClass()) {
284: final EmbeddedClassIntrospector<Object> emci = this .inspector
285: .getEmbeddedPKClass();
286: pkProperties = emci.getPersitentProperties();
287: } else {
288: pkProperties = new ArrayList<Property>(inspector
289: .getPkFields());
290: }
291:
292: // process the pk list
293: for (int i = 0; i < pkProperties.size(); i++) {
294: final Property aktProperty = pkProperties.get(i);
295: aktFieldName = aktProperty.getName();
296: if (this .inspector.hasEmbeddedPKClass()) {
297: sb.append(this .inspector.getEmbeddedPKClass()
298: .getPresistentFieldInfo(aktProperty)
299: .getDbName());
300: sb.append("=");
301: Object pkClassInstance = this .getField(toCreate,
302: this .inspector.getEmbeddedPKClass()
303: .getAttibuteName());
304: sb.append(this .getField(pkClassInstance,
305: aktProperty));
306: } else {
307: sb.append(this .inspector.getPresistentFieldInfo(
308: aktProperty).getDbName());
309: sb.append("=");
310: sb.append(this .getField(toCreate, aktProperty));
311: }
312:
313: // is not last one?
314: if (i + 1 < pkProperties.size()) {
315: sb.append(" AND ");
316: }
317: }
318:
319: return sb.toString();
320: } catch (IllegalAccessException e) {
321: log.error("Canīt read the field: " + aktFieldName);
322: throw new RuntimeException("Canīt read the field: "
323: + aktFieldName);
324: }
325: }
326:
327: /**
328: * True if this generator is the root
329: *
330: * @return - true if the generator is root generator
331: */
332: private boolean isRootGenerator() {
333: return this .rootGenerator == null;
334: }
335:
336: @SuppressWarnings("unchecked")
337: private void processManyToOneRelation(EntityReleationInfo eri,
338: T toCreate) {
339: if (eri.getReleationType() == RelationType.ManyToOne) {
340: ManyToOneReleation o2m = (ManyToOneReleation) eri;
341: try {
342: final Object relatedObject = o2m.getSourceProperty()
343: .getField(toCreate);
344: if (relatedObject != null) {
345: final UndoScriptGenerator innerUndo = this
346: .getInnerUndoScriptGen(
347: o2m.getTargetClass(),
348: DeleteOrder.DELETE_AFTER);
349: // protokoll assotiated object
350: innerUndo.protokollCreate(relatedObject);
351: }
352: } catch (IllegalAccessException e) {
353: throw new RuntimeException(
354: "Canīt generate undo script for One to Many relation");
355: }
356: }
357: }
358:
359: @SuppressWarnings("unchecked")
360: private void processOneToManyRelation(EntityReleationInfo eri,
361: T toCreate) {
362: if (eri.getReleationType() == RelationType.OneToMany) {
363: OneToManyReleation o2m = (OneToManyReleation) eri;
364: // no need for subs delete scripts if cascae type is all
365: try {
366: final Collection relatedObjects = (Collection) o2m
367: .getSourceProperty().getField(toCreate);
368: if (relatedObjects != null) {
369: final UndoScriptGenerator innerUndo = this
370: .getInnerUndoScriptGen(
371: o2m.getTargetClass(),
372: DeleteOrder.DELETE_FIRST);
373: // protokoll assotiated objects
374: for (Object akt : relatedObjects) {
375: innerUndo.protokollCreate(akt);
376: }
377: }
378:
379: } catch (IllegalAccessException e) {
380: throw new RuntimeException(
381: "Canīt generate undo script for One to Many relation");
382: }
383: }
384:
385: }
386:
387: /**
388: * If a object has relations to other entity beans, this method will create
389: * undo scripts for this objects also
390: *
391: * @param eri -
392: * the entity relation info
393: * @param toCreate -
394: * the main bean to create
395: */
396: @SuppressWarnings("unchecked")
397: private void processReletatedObjects(EntityReleationInfo eri,
398: T toCreate) {
399: // TODO generate undo relations for OneToOne
400: processOneToManyRelation(eri, toCreate);
401: processManyToOneRelation(eri, toCreate);
402: }
403: }
|