View Javadoc
1   /*
2    * (c) Copyright 2006-2020 by rapiddweller GmbH & Volker Bergmann. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, is permitted under the terms of the
6    * GNU General Public License.
7    *
8    * For redistributing this software or a derivative work under a license other
9    * than the GPL-compatible Free Software License as defined by the Free
10   * Software Foundation or approved by OSI, you must first obtain a commercial
11   * license to this software product from rapiddweller GmbH & Volker Bergmann.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
15   * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
17   * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGE.
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   * Cascades the 'transcode' operation to all entities configured to be related
66   * to the currently transcoded entity.<br/><br/>
67   * Created: 18.04.2011 07:14:34
68   *
69   * @author Volker Bergmann
70   * @since 0.6.6
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     * The Type expression.
83     */
84    final MutatingTypeExpression typeExpression;
85    /**
86     * The Type.
87     */
88    ComplexTypeDescriptor type;
89  
90    /**
91     * Instantiates a new Cascade statement.
92     *
93     * @param ref            the ref
94     * @param typeExpression the type expression
95     * @param parent         the parent
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     // iterate rows
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   // implementation --------------------------------------------------------------------------------------------------
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    * The type Reference.
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      * Instantiates a new Reference.
252      *
253      * @param refererTableName the referer table name
254      * @param columnNames      the column names
255      */
256     public Reference(String refererTableName, String[] columnNames) {
257       this.refererTableName = refererTableName;
258       this.columnNames = columnNames;
259     }
260 
261     /**
262      * Gets target table name.
263      *
264      * @param parentTable the parent table
265      * @param db          the db
266      * @param context     the context
267      * @return the target table name
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      * Resolve references data iterator.
280      *
281      * @param currentEntity the current entity
282      * @param db            the db
283      * @param context       the context
284      * @return the data iterator
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); // including self-recursion
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      * Resolve to many reference data iterator.
312      *
313      * @param fromEntity the from entity
314      * @param fk         the fk
315      * @param db         the db
316      * @param context    the context
317      * @return the data iterator
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      * Resolve to one reference data iterator.
336      *
337      * @param fromEntity the from entity
338      * @param fk         the fk
339      * @param db         the db
340      * @param context    the context
341      * @return the data iterator
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      * Parse reference.
360      *
361      * @param refSpec the ref spec
362      * @return the reference
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         // parse table name
370         int token = tokenizer.nextToken();
371         if (token != TT_WORD) {
372           throw new SyntaxError(REF_SYNTAX_MESSAGE, refSpec);
373         }
374         // this must be at this position!
375         String tableName = tokenizer.sval;
376 
377         // parse column names
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 }