1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package com.rapiddweller.benerator.engine.statement;
28
29 import com.rapiddweller.benerator.composite.ComponentAndVariableSupport;
30 import com.rapiddweller.benerator.composite.GeneratorComponent;
31 import com.rapiddweller.benerator.engine.BeneratorContext;
32 import com.rapiddweller.benerator.factory.ComplexTypeGeneratorFactory;
33 import com.rapiddweller.common.ArrayBuilder;
34 import com.rapiddweller.common.ArrayFormat;
35 import com.rapiddweller.common.ConfigurationError;
36 import com.rapiddweller.common.Context;
37 import com.rapiddweller.common.IOUtil;
38 import com.rapiddweller.common.SyntaxError;
39 import com.rapiddweller.format.DataContainer;
40 import com.rapiddweller.format.DataIterator;
41 import com.rapiddweller.jdbacl.identity.IdentityModel;
42 import com.rapiddweller.jdbacl.identity.IdentityProvider;
43 import com.rapiddweller.jdbacl.identity.KeyMapper;
44 import com.rapiddweller.jdbacl.identity.NoIdentity;
45 import com.rapiddweller.jdbacl.model.DBForeignKeyConstraint;
46 import com.rapiddweller.jdbacl.model.DBTable;
47 import com.rapiddweller.jdbacl.model.Database;
48 import com.rapiddweller.model.data.ComplexTypeDescriptor;
49 import com.rapiddweller.model.data.Entity;
50 import com.rapiddweller.model.data.InstanceDescriptor;
51 import com.rapiddweller.model.data.ReferenceDescriptor;
52 import com.rapiddweller.model.data.Uniqueness;
53 import com.rapiddweller.platform.db.DBSystem;
54 import org.apache.logging.log4j.LogManager;
55 import org.apache.logging.log4j.Logger;
56
57 import java.io.IOException;
58 import java.io.StreamTokenizer;
59 import java.io.StringReader;
60 import java.util.List;
61
62 import static java.io.StreamTokenizer.TT_WORD;
63
64
65
66
67
68
69
70
71
72 public class CascadeStatement extends SequentialStatement implements CascadeParent {
73
74 private static final Logger LOGGER = LogManager.getLogger(CascadeStatement.class);
75
76 private static final String REF_SYNTAX_MESSAGE = "Expected Syntax: table(column1, column2, ...)";
77
78 private final CascadeParent parent;
79 private final Reference ref;
80 private Entity currentEntity;
81
82
83
84 final MutatingTypeExpression typeExpression;
85
86
87
88 ComplexTypeDescriptor type;
89
90
91
92
93
94
95
96
97 public CascadeStatement(String ref, MutatingTypeExpression typeExpression, CascadeParent parent) {
98 this.typeExpression = typeExpression;
99 this.ref = Reference.parse(ref);
100 this.parent = parent;
101 this.currentEntity = null;
102 }
103
104 @Override
105 public boolean execute(BeneratorContext context) {
106 DBSystem source = getSource(context);
107 getType(source, context);
108 IdentityModel identity = parent.getIdentityProvider().getIdentity(type.getName(), false);
109 String tableName = type.getName();
110 LOGGER.debug("Cascading transcode from " + parent.currentEntity().type() + " to " + tableName);
111
112
113 List<GeneratorComponent<Entity>> generatorComponents =
114 ComplexTypeGeneratorFactory.createMutatingGeneratorComponents(type, Uniqueness.NONE, context);
115 ComponentAndVariableSupport<Entity> cavs = new ComponentAndVariableSupport<>(tableName,
116 generatorComponents, context);
117 cavs.init(context);
118
119 DataIterator<Entity> iterator = ref.resolveReferences(parent.currentEntity(), source, context);
120 DataContainer<Entity> container = new DataContainer<>();
121 while ((container = iterator.next(container)) != null) {
122 mutateAndTranscodeEntity(container.getData(), identity, cavs, context);
123 }
124 IOUtil.close(iterator);
125 return true;
126 }
127
128 @Override
129 public DBSystem getSource(BeneratorContext context) {
130 return parent.getSource(context);
131 }
132
133 @Override
134 public Entity currentEntity() {
135 return currentEntity;
136 }
137
138 @Override
139 public KeyMapper getKeyMapper() {
140 return parent.getKeyMapper();
141 }
142
143 @Override
144 public IdentityProvider getIdentityProvider() {
145 return parent.getIdentityProvider();
146 }
147
148 @Override
149 public boolean needsNkMapping(String type) {
150 return parent.needsNkMapping(type);
151 }
152
153 @Override
154 public DBSystem getTarget(BeneratorContext context) {
155 return parent.getTarget(context);
156 }
157
158 @Override
159 public ComplexTypeDescriptor getType(DBSystem db, BeneratorContext context) {
160 if (type == null) {
161 String parentType = parent.getType(db, context).getName();
162 typeExpression.setTypeName(ref.getTargetTableName(parentType, db, context));
163 type = typeExpression.evaluate(context);
164 }
165 return type;
166 }
167
168
169
170 private void mutateAndTranscodeEntity(Entity sourceEntity, IdentityModel identity, ComponentAndVariableSupport<Entity> cavs,
171 BeneratorContext context) {
172 Object sourcePK = sourceEntity.idComponentValues();
173 boolean mapNk = parent.needsNkMapping(sourceEntity.type());
174 String nk = null;
175 KeyMapper mapper = getKeyMapper();
176 DBSystem source = getSource(context);
177 if (mapNk) {
178 nk = mapper.getNaturalKey(source.getId(), identity, sourcePK);
179 }
180 Entityity.html#Entity">Entity targetEntity = new Entity(sourceEntity);
181 cavs.apply(targetEntity, context);
182 Object targetPK = targetEntity.idComponentValues();
183 transcodeForeignKeys(targetEntity, source, context);
184 mapper.store(source.getId(), identity, nk, sourcePK, targetPK);
185 getTarget(context).store(targetEntity);
186 LOGGER.debug("transcoded {} to {}", sourceEntity, targetEntity);
187 cascade(sourceEntity, context);
188 }
189
190 private void transcodeForeignKeys(Entity entity, DBSystem source, Context context) {
191 ComplexTypeDescriptor tableDescriptor = entity.descriptor();
192 for (InstanceDescriptor component : tableDescriptor.getParts()) {
193 if (component instanceof ReferenceDescriptor) {
194 ReferenceDescriptor../../com/rapiddweller/model/data/ReferenceDescriptor.html#ReferenceDescriptor">ReferenceDescriptor fk = (ReferenceDescriptor) component;
195 String refereeTableName = fk.getTargetType();
196 Object sourceRef = entity.get(fk.getName());
197 if (sourceRef != null) {
198 IdentityProvider identityProvider = parent.getIdentityProvider();
199 IdentityModel sourceIdentity = identityProvider.getIdentity(refereeTableName, false);
200 if (sourceIdentity == null) {
201 DBTable refereeTable = source.getDbMetaData().getTable(refereeTableName);
202 sourceIdentity = new NoIdentity(refereeTable.getName());
203 identityProvider.registerIdentity(sourceIdentity, refereeTableName);
204 }
205
206 boolean needsNkMapping = parent.needsNkMapping(refereeTableName);
207 if (sourceIdentity instanceof NoIdentity && needsNkMapping) {
208 throw new ConfigurationError("No identity defined for table " + refereeTableName);
209 }
210 KeyMapper mapper = parent.getKeyMapper();
211 Object targetRef;
212 if (needsNkMapping) {
213 String sourceRefNK = mapper.getNaturalKey(source.getId(), sourceIdentity, sourceRef);
214 targetRef = mapper.getTargetPK(sourceIdentity, sourceRefNK);
215 } else {
216 targetRef = mapper.getTargetPK(source.getId(), sourceIdentity, sourceRef);
217 }
218 if (targetRef == null) {
219 String message = "No mapping found for " + source.getId() + '.' + refereeTableName + "#" + sourceRef +
220 " referred in " + entity.type() + "(" + fk.getName() + "). " +
221 "Probably has not been in the result set of the former '" + refereeTableName + "' nk query.";
222 getErrorHandler(context).handleError(message);
223 }
224 entity.setComponent(fk.getName(), targetRef);
225 }
226 }
227 }
228 }
229
230 private void cascade(Entity sourceEntity, BeneratorContext context) {
231 this.currentEntity = sourceEntity;
232 executeSubStatements(context);
233 this.currentEntity = null;
234 }
235
236
237
238
239 public static class Reference {
240
241 private final String refererTableName;
242 private final String[] columnNames;
243
244 private DBForeignKeyConstraint fk;
245 private Database database;
246 private DBTable refererTable;
247 private DBTable refereeTable;
248 private DBTable targetTable;
249
250
251
252
253
254
255
256 public Reference(String refererTableName, String[] columnNames) {
257 this.refererTableName = refererTableName;
258 this.columnNames = columnNames;
259 }
260
261
262
263
264
265
266
267
268
269 public String getTargetTableName(String parentTable, DBSystem db, BeneratorContext context) {
270 if (!parentTable.equals(refererTableName)) {
271 return refererTableName;
272 } else {
273 initIfNecessary(parentTable, db, context);
274 return targetTable.getName();
275 }
276 }
277
278
279
280
281
282
283
284
285
286 public DataIterator<Entity> resolveReferences(Entity currentEntity, DBSystem db, BeneratorContext context) {
287 initIfNecessary(currentEntity.type(), db, context);
288 DBTable parentTable = database.getTable(currentEntity.type());
289 if (parentTable.equals(refereeTable)) {
290 return resolveToManyReference(currentEntity, fk, db, context);
291 } else if (parentTable.equals(refererTable)) {
292 return resolveToOneReference(currentEntity, fk, db, context);
293 } else {
294 throw new ConfigurationError("Table '" + parentTable + "' does not relate to the foreign key " +
295 refererTableName + '(' + ArrayFormat.format(columnNames) + ')');
296 }
297 }
298
299 private void initIfNecessary(String parentTable, DBSystem db, BeneratorContext context) {
300 if (this.database != null) {
301 return;
302 }
303 this.database = db.getDbMetaData();
304 this.refererTable = this.database.getTable(refererTableName);
305 this.fk = refererTable.getForeignKeyConstraint(columnNames);
306 this.refereeTable = fk.getRefereeTable();
307 this.targetTable = (parentTable.equalsIgnoreCase(refereeTable.getName()) ? refererTable : refereeTable);
308 }
309
310
311
312
313
314
315
316
317
318
319 DataIterator<Entity> resolveToManyReference(
320 Entity fromEntity, DBForeignKeyConstraint fk, DBSystem db, BeneratorContext context) {
321 StringBuilder selector = new StringBuilder();
322 String[] refererColumnNames = fk.getColumnNames();
323 String[] refereeColumnNames = fk.getRefereeColumnNames();
324 for (int i = 0; i < refererColumnNames.length; i++) {
325 if (selector.length() > 0) {
326 selector.append(" and ");
327 }
328 Object refereeColumnValue = fromEntity.get(refereeColumnNames[i]);
329 selector.append(refererColumnNames[i]).append('=').append(db.getDialect().formatValue(refereeColumnValue));
330 }
331 return db.queryEntities(fk.getTable().getName(), selector.toString(), context).iterator();
332 }
333
334
335
336
337
338
339
340
341
342
343 DataIterator<Entity> resolveToOneReference(
344 Entity fromEntity, DBForeignKeyConstraint fk, DBSystem db, BeneratorContext context) {
345 StringBuilder selector = new StringBuilder();
346 String[] refererColumnNames = fk.getColumnNames();
347 String[] refereeColumnNames = fk.getRefereeColumnNames();
348 for (int i = 0; i < refererColumnNames.length; i++) {
349 if (selector.length() > 0) {
350 selector.append(" and ");
351 }
352 Object refererColumnValue = fromEntity.get(refererColumnNames[i]);
353 selector.append(refereeColumnNames[i]).append('=').append(db.getDialect().formatValue(refererColumnValue));
354 }
355 return db.queryEntities(fk.getRefereeTable().getName(), selector.toString(), context).iterator();
356 }
357
358
359
360
361
362
363
364 @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
365 static Reference parse(String refSpec) {
366 StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(refSpec));
367 tokenizer.wordChars('_', '_');
368 try {
369
370 int token = tokenizer.nextToken();
371 if (token != TT_WORD) {
372 throw new SyntaxError(REF_SYNTAX_MESSAGE, refSpec);
373 }
374
375 String tableName = tokenizer.sval;
376
377
378 if ((token = tokenizer.nextToken()) != '(') {
379 throw new SyntaxError(REF_SYNTAX_MESSAGE, refSpec);
380 }
381 ArrayBuilder<String> columnNames = new ArrayBuilder<>(String.class);
382 do {
383 if ((token = tokenizer.nextToken()) != TT_WORD) {
384 throw new SyntaxError(REF_SYNTAX_MESSAGE, refSpec);
385 }
386 columnNames.add(tokenizer.sval);
387 token = tokenizer.nextToken();
388 if (token != ',' && token != ')') {
389 throw new SyntaxError(REF_SYNTAX_MESSAGE, refSpec);
390 }
391 } while (token == ',');
392 if (token != ')') {
393 throw new SyntaxError("reference definition must end with ')'", refSpec);
394 }
395 return new Reference(tableName, columnNames.toArray());
396 } catch (IOException e) {
397 throw new SyntaxError(REF_SYNTAX_MESSAGE, refSpec);
398 }
399 }
400
401 }
402
403 }