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.gui;
28
29 import com.rapiddweller.benerator.archetype.FolderLayout;
30 import com.rapiddweller.benerator.main.DBSnapshotTool;
31 import com.rapiddweller.common.CollectionUtil;
32 import com.rapiddweller.common.ConfigurationError;
33 import com.rapiddweller.common.Context;
34 import com.rapiddweller.common.Encodings;
35 import com.rapiddweller.common.FileUtil;
36 import com.rapiddweller.common.IOUtil;
37 import com.rapiddweller.common.OrderedMap;
38 import com.rapiddweller.common.StringUtil;
39 import com.rapiddweller.common.SystemInfo;
40 import com.rapiddweller.common.accessor.GraphAccessor;
41 import com.rapiddweller.common.context.DefaultContext;
42 import com.rapiddweller.common.converter.ToStringConverter;
43 import com.rapiddweller.common.maven.MavenUtil;
44 import com.rapiddweller.common.ui.I18NError;
45 import com.rapiddweller.common.ui.ProgressMonitor;
46 import com.rapiddweller.common.version.VersionInfo;
47 import com.rapiddweller.format.html.parser.DefaultHTMLTokenizer;
48 import com.rapiddweller.format.html.parser.HTMLTokenizer;
49 import com.rapiddweller.format.text.LFNormalizingStringBuilder;
50 import com.rapiddweller.model.data.ComplexTypeDescriptor;
51 import com.rapiddweller.model.data.ComponentDescriptor;
52 import com.rapiddweller.model.data.DataModel;
53 import com.rapiddweller.model.data.FeatureDetail;
54 import com.rapiddweller.model.data.IdDescriptor;
55 import com.rapiddweller.model.data.InstanceDescriptor;
56 import com.rapiddweller.model.data.PartDescriptor;
57 import com.rapiddweller.model.data.ReferenceDescriptor;
58 import com.rapiddweller.model.data.SimpleTypeDescriptor;
59 import com.rapiddweller.model.data.TypeDescriptor;
60 import com.rapiddweller.platform.db.DBSystem;
61 import com.rapiddweller.platform.db.DefaultDBSystem;
62 import com.rapiddweller.script.Expression;
63 import com.rapiddweller.script.expression.ExpressionUtil;
64
65 import java.io.BufferedReader;
66 import java.io.File;
67 import java.io.FileNotFoundException;
68 import java.io.IOException;
69 import java.text.ParseException;
70 import java.util.ArrayList;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.Set;
74
75 import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_CONSUMER;
76 import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_NAME;
77 import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_SOURCE;
78 import static com.rapiddweller.benerator.engine.DescriptorConstants.ATT_TYPE;
79 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_ATTRIBUTE;
80 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_COMMENT;
81 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_DATABASE;
82 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_EXECUTE;
83 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_GENERATE;
84 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_ID;
85 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_REFERENCE;
86 import static com.rapiddweller.benerator.engine.DescriptorConstants.EL_SETUP;
87
88
89
90
91
92
93
94
95 public class ProjectBuilder implements Runnable {
96
97 private static final String TAB = " ";
98 private static final String DBUNIT_SNAPSHOT_FILENAME = "base.dbunit.xml";
99 private static final String COMMENT_SNAPSHOT = "Create a valid predefined base data set for regression testing " +
100 "by importing a snapshot file";
101 private static final String COMMENT_DROP_TABLES = "Drop the current tables and sequences if they already exist";
102 private static final String COMMENT_CREATE_TABLES = "Create the tables and sequences";
103
104
105 private static final Set<String> DB_CONSTRAINT_NAMES = CollectionUtil.toSet("nullable", "maxLength", "type");
106 private static final ToStringConverter toStringConverter = new ToStringConverter("");
107
108
109
110
111 protected final Setup setup;
112 private final List<Exception> errors;
113 private final ProgressMonitor monitor;
114 private final FolderLayout folderLayout;
115 private final DataModel dataModel;
116
117
118
119 protected TypeDescriptor[] descriptors;
120
121
122
123 protected DBSystem db;
124
125
126
127
128
129
130
131
132 public ProjectBuilder(Setup setup, FolderLayout folderLayout, ProgressMonitor monitor) {
133 this.setup = setup;
134 this.errors = new ArrayList<>();
135 this.monitor = monitor;
136 this.descriptors = new TypeDescriptor[0];
137 this.folderLayout = folderLayout;
138 this.dataModel = new DataModel();
139 }
140
141 private static Map<String, String> defineDbAttributes(Setup setup, DefaultHTMLTokenizer tokenizer) {
142 Map<String, String> attributes = tokenizer.attributes();
143 attributes.put("url", setup.getDbUrl());
144 attributes.put("driver", setup.getDbDriver());
145 if (!StringUtil.isEmpty(setup.getDbSchema())) {
146 attributes.put("schema", setup.getDbSchema());
147 }
148 attributes.put("user", setup.getDbUser());
149 if (!StringUtil.isEmpty(setup.getDbPassword())) {
150 attributes.put("password", setup.getDbPassword());
151 }
152 return attributes;
153 }
154
155 private static void appendStartTag(String nodeName, Map<String, String> attributes,
156 LFNormalizingStringBuilder writer, boolean wrapAttribs) {
157 writer.append('<').append(nodeName);
158 writeAttributes(attributes, writer, wrapAttribs);
159 writer.append('>');
160 }
161
162 private static Map<String, String> defineSetupAttributes(DefaultHTMLTokenizer tokenizer,
163 Setup setup) {
164 Map<String, String> attributes = tokenizer.attributes();
165 if (setup.getEncoding() != null) {
166 attributes.put("defaultEncoding", setup.getEncoding());
167 }
168 if (setup.getDataset() != null) {
169 attributes.put("defaultDataset", setup.getDataset());
170 }
171 if (setup.getLocale() != null) {
172 attributes.put("defaultLocale", setup.getLocale());
173 }
174 if (setup.getLineSeparator() != null) {
175 attributes.put("defaultLineSeparator", StringUtil.escape(setup.getLineSeparator()));
176 }
177 return attributes;
178 }
179
180 private static void appendElement(String nodeName, Map<String, String> attributes, LFNormalizingStringBuilder writer, boolean wrapAttribs) {
181 writer.append('<').append(nodeName);
182 writeAttributes(attributes, writer, wrapAttribs);
183 writer.append("/>");
184 }
185
186 private static void writeAttributes(Map<String, String> attributes, LFNormalizingStringBuilder writer, boolean wrapAttribs) {
187 int i = 0;
188 for (Map.Entry<String, String> attribute : attributes.entrySet()) {
189 if (wrapAttribs && i > 0) {
190 writer.append(TAB).append(TAB);
191 } else {
192 writer.append(' ');
193 }
194 writer.append(attribute.getKey()).append("=\"").append(attribute.getValue()).append("\"");
195 if (wrapAttribs && i < attributes.size() - 1) {
196 writer.append('\n');
197 }
198 i++;
199 }
200 }
201
202 private static void copyToProject(File srcFile, File projectFolder) throws IOException {
203 File dstFile = new File(projectFolder, srcFile.getName());
204 FileUtil.copy(srcFile, dstFile, true);
205 }
206
207 private static void processComment(DefaultHTMLTokenizer tokenizer, Setup setup,
208 LFNormalizingStringBuilder writer) throws IOException, ParseException {
209 String startText = tokenizer.text();
210 int nextToken = tokenizer.nextToken();
211 if (nextToken == HTMLTokenizer.END_TAG) {
212 writer.append(startText).append(tokenizer.text());
213 return;
214 }
215 if (nextToken != HTMLTokenizer.TEXT) {
216 throw new ParseException("Text expected in comment", -1);
217 }
218 String commentText = tokenizer.text().trim();
219 if ((COMMENT_DROP_TABLES.equals(commentText) && setup.getDropScriptFile() == null)
220 || (COMMENT_CREATE_TABLES.equals(commentText) && setup.getCreateScriptFile() == null)
221 || (COMMENT_SNAPSHOT.equals(commentText) && setup.getDbSnapshot() == null)) {
222 while (tokenizer.nextToken() != HTMLTokenizer.END_TAG) {
223
224 }
225 } else {
226
227 writer.append(startText).append(tokenizer.text());
228 }
229 }
230
231 private static boolean isDefaultValue(Object value, String name) {
232 return "nullable".equals(name) && (value == null || ((Boolean) value));
233 }
234
235 private static void appendEndElement(String nodeName, LFNormalizingStringBuilder writer) {
236 writer.append("</").append(nodeName).append(">");
237 }
238
239 private static void format(FeatureDetail<?> detail, Map<String, String> attributes) {
240 if (!ATT_NAME.equals(detail.getName()) && detail.getValue() != null && !isDbConstraint(detail.getName())) {
241 attributes.put(detail.getName(), toStringConverter.convert(detail.getValue()));
242 }
243 }
244
245 private static boolean isDbConstraint(String name) {
246 return DB_CONSTRAINT_NAMES.contains(name);
247 }
248
249 @Override
250 public void run() {
251 try {
252
253 if (setup.isDatabaseProject()) {
254 parseDatabaseMetaData();
255 if (monitor != null) {
256 monitor.setMaximum(5 + descriptors.length);
257 }
258 advanceMonitor();
259 } else {
260 monitor.setMaximum(5);
261 }
262
263 createFolderLayout();
264 applyArchetype(setup, monitor);
265 createPOM();
266 createProjectPropertiesFile();
267
268
269 copyImportFiles();
270
271
272 Exception exception = null;
273 if (setup.isDatabaseProject() && !"none".equals(setup.getDbSnapshot()) && !setup.isShopProject()) {
274 try {
275 createDbSnapshot();
276 } catch (Exception e) {
277 exception = e;
278 }
279 }
280
281
282 createBeneratorXml();
283
284 createEclipseProject();
285
286 if (exception != null) {
287 throw exception;
288 }
289 } catch (Exception e) {
290 errors.add(e);
291 e.printStackTrace();
292 } finally {
293 if (db != null) {
294 db.close();
295 }
296 if (monitor != null) {
297 monitor.setProgress(monitor.getMaximum());
298 }
299 }
300 }
301
302 private void parseDatabaseMetaData() {
303 noteMonitor("scanning database");
304 if (monitor != null) {
305 monitor.setProgress(0);
306 }
307 db = getDBSystem(setup);
308 descriptors = db.getTypeDescriptors();
309 }
310
311 private void createFolderLayout() {
312 String groupId = setup.getGroupId();
313 String pkgFolder = "/" + (StringUtil.isEmpty(groupId) ? "" : groupId.replace('.', '/') + '/') + setup.getProjectName();
314 haveSubFolder("src/main/java" + pkgFolder);
315 haveSubFolder("src/main/resources" + pkgFolder);
316 haveSubFolder("src/test/java" + pkgFolder);
317 haveSubFolder("src/test/resources" + pkgFolder);
318 }
319
320 private void applyArchetype(Setup setup, ProgressMonitor monitor) throws IOException {
321
322 monitor.setNote("Creating files...");
323 setup.getArchetype().copyFilesTo(setup.getProjectFolder(), folderLayout);
324 }
325
326 private void createEclipseProject() {
327 setup.projectFile(".project");
328 if (setup.isEclipseProject()) {
329 noteMonitor("Creating Eclipse project");
330 MavenUtil.invoke("eclipse:eclipse", setup.getProjectFolder(), !setup.isOffline());
331 }
332 advanceMonitor();
333 }
334
335 private void haveSubFolder(String relativePath) {
336 FileUtil.ensureDirectoryExists(setup.subDirectory(folderLayout.mapSubFolder(relativePath)));
337 }
338
339
340
341
342
343
344 public Exception[] getErrors() {
345 return CollectionUtil.toArray(errors, Exception.class);
346 }
347
348 private void advanceMonitor() {
349 if (monitor != null) {
350 monitor.advance();
351 }
352 }
353
354 private void noteMonitor(String note) {
355 if (monitor != null) {
356 monitor.setNote(note);
357 }
358 }
359
360 private void copyImportFiles() {
361 if (!setup.isShopProject()) {
362 copyImportFile(setup.getDropScriptFile());
363 copyImportFile(setup.getCreateScriptFile());
364 }
365 }
366
367 private void copyImportFile(File importFile) {
368 if (importFile == null) {
369 return;
370 }
371 if (importFile.exists()) {
372 noteMonitor("Importing " + importFile);
373 File copy = setup.projectFile(importFile.getName());
374 try {
375 IOUtil.copyFile(importFile.getAbsolutePath(), copy.getAbsolutePath());
376 } catch (IOException e) {
377 throw new I18NError("ErrorCopying", e, importFile.getAbsolutePath(), copy);
378 }
379 } else {
380 errors.add(new I18NError("FileNotFound",
381 new FileNotFoundException(importFile.getAbsolutePath()), importFile));
382 }
383 advanceMonitor();
384 }
385
386 private void createDbSnapshot() {
387 String format = setup.getDbSnapshot();
388 File file = setup.projectFile(setup.getDbSnapshotFile());
389 DBSnapshotTool.export(setup.getDbUrl(), setup.getDbDriver(), setup.getDbSchema(),
390 setup.getDbUser(), setup.getDbPassword(), file.getAbsolutePath(), setup.getEncoding(), format,
391 null, monitor);
392 }
393
394 private void createPOM() {
395 noteMonitor("creating pom.xml");
396 resolveVariables(new File(setup.getProjectFolder(), "pom.xml"));
397 }
398
399 private void createProjectPropertiesFile() {
400 String filename = "benerator.properties";
401 File file = new File(setup.getProjectFolder(), filename);
402 if (file.exists()) {
403 noteMonitor("creating " + filename);
404 resolveVariables(file);
405 }
406 }
407
408
409
410
411
412
413
414 public File resolveVariables(File file) {
415 try {
416 String content = IOUtil.getContentOfURI(file.getAbsolutePath());
417 content = resolveVariables(content);
418 file.delete();
419 IOUtil.writeTextFile(file.getAbsolutePath(), content, getXMLEncoding());
420 return file;
421 } catch (IOException e) {
422 throw new I18NError("ErrorCreatingFile", e, file);
423 } finally {
424 advanceMonitor();
425 }
426 }
427
428 private String getXMLEncoding() {
429 String configuredEncoding = setup.getEncoding();
430 return (StringUtil.isEmpty(configuredEncoding) ? Encodings.UTF_8 : configuredEncoding);
431 }
432
433 private String resolveVariables(String content) {
434 return replaceVariables(content);
435 }
436
437
438
439
440
441
442
443 public void createBeneratorXml() throws IOException, ParseException {
444 File descriptorFile = new File(setup.getProjectFolder(), "benerator.xml");
445 if (descriptorFile.exists()) {
446 BufferedReader reader = IOUtil.getReaderForURI(descriptorFile.getAbsolutePath());
447 DefaultHTMLTokenizer tokenizer = new DefaultHTMLTokenizer(reader);
448 String lineSeparator = setup.getLineSeparator();
449 if (StringUtil.isEmpty(lineSeparator)) {
450 lineSeparator = SystemInfo.getLineSeparator();
451 }
452 LFNormalizingStringBuilder writer = new LFNormalizingStringBuilder(lineSeparator);
453 while (tokenizer.nextToken() != HTMLTokenizer.END) {
454 processToken(setup, tokenizer, writer);
455 }
456 String xml = writer.toString();
457 xml = resolveVariables(xml);
458 IOUtil.writeTextFile(descriptorFile.getAbsolutePath(), xml, "UTF-8");
459 }
460 monitor.advance();
461 }
462
463 private void processToken(Setup setup,
464 DefaultHTMLTokenizer tokenizer, LFNormalizingStringBuilder writer)
465 throws IOException, ParseException {
466
467 switch (tokenizer.tokenType()) {
468 case HTMLTokenizer.START_TAG: {
469 String nodeName = tokenizer.name();
470 if (EL_SETUP.equals(nodeName)) {
471 appendStartTag(nodeName, defineSetupAttributes(tokenizer, setup), writer, true);
472 } else if (EL_COMMENT.equals(nodeName)) {
473 processComment(tokenizer, setup, writer);
474 } else {
475 writer.append(tokenizer.text());
476 }
477 break;
478 }
479 case HTMLTokenizer.CLOSED_TAG: {
480 String nodeName = tokenizer.name();
481 if (EL_DATABASE.equals(nodeName) && setup.isDatabaseProject()) {
482 appendElement(nodeName, defineDbAttributes(setup, tokenizer), writer, true);
483 } else if (EL_EXECUTE.equals(nodeName)) {
484 Map<String, String> attributes = tokenizer.attributes();
485 String uri = attributes.get("uri");
486 if ("{drop_tables.sql}".equals(uri)) {
487 if (setup.getDropScriptFile() != null) {
488 File dropScriptFile = setup.getDropScriptFile();
489 copyToProject(dropScriptFile, setup.getProjectFolder());
490 attributes.put("uri", dropScriptFile.getName());
491 appendElement(nodeName, attributes, writer, false);
492 }
493 } else if ("{create_tables.sql}".equals(uri)) {
494 if (setup.getCreateScriptFile() != null) {
495 File createScriptFile = setup.getCreateScriptFile();
496 copyToProject(createScriptFile, setup.getProjectFolder());
497 attributes.put("uri", createScriptFile.getName());
498 appendElement(nodeName, attributes, writer, false);
499 }
500 } else {
501 writer.append(tokenizer.text());
502 }
503 } else if (EL_GENERATE.equals(nodeName)) {
504 Map<String, String> attributes = tokenizer.attributes();
505 if (DBUNIT_SNAPSHOT_FILENAME.equals(attributes.get(ATT_SOURCE))) {
506 if (setup.getDbSnapshot() != null) {
507 appendElement(nodeName, attributes, writer, false);
508 }
509 } else if ("tables".equals(attributes.get(ATT_TYPE))) {
510 generateTables(setup, writer);
511 } else {
512 writer.append(tokenizer.text());
513 }
514 } else {
515 writer.append(tokenizer.text());
516 }
517 break;
518 }
519 default:
520 writer.append(tokenizer.text());
521 }
522 }
523
524 private void generateTables(Setup setup, LFNormalizingStringBuilder writer) {
525 DBSystem db = getDBSystem(setup);
526 TypeDescriptor[] descriptors = db.getTypeDescriptors();
527 for (TypeDescriptor descriptor : descriptors) {
528 ComplexTypeDescriptorddweller/model/data/ComplexTypeDescriptor.html#ComplexTypeDescriptor">ComplexTypeDescriptor complexType = (ComplexTypeDescriptor) descriptor;
529 final String name = complexType.getName();
530 InstanceDescriptorcriptor.html#InstanceDescriptor">InstanceDescriptor iDesc = new InstanceDescriptor(name, db, complexType);
531 if (setup.getDbSnapshot() != null) {
532 iDesc.setCount(ExpressionUtil.constant(0L));
533 } else {
534 iDesc.setCount(ExpressionUtil.constant(db.countEntities(name)));
535 }
536 generateTable(iDesc, writer);
537 }
538 }
539
540 private DBSystem getDBSystem(Setup setup) {
541 DBSystem db = new DefaultDBSystem("db", setup.getDbUrl(), setup.getDbDriver(), setup.getDbUser(), setup.getDbPassword(), dataModel);
542 if (setup.getDbSchema() != null) {
543 db.setSchema(setup.getDbSchema());
544 }
545 dataModel.addDescriptorProvider(db);
546 return db;
547 }
548
549 @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
550 private void generateTable(InstanceDescriptor descriptor, LFNormalizingStringBuilder writer) {
551 ComplexTypeDescriptorom/rapiddweller/model/data/ComplexTypeDescriptor.html#ComplexTypeDescriptor">ComplexTypeDescriptor type = (ComplexTypeDescriptor) descriptor.getTypeDescriptor();
552 Map<String, String> attributes = new OrderedMap<>();
553 for (FeatureDetail<?> detail : descriptor.getDetails()) {
554 Object value = detail.getValue();
555 if (value != null && !isDefaultValue(value, detail.getName())) {
556 if (value instanceof Expression) {
557 value = ((Expression<?>) value).evaluate(null);
558 }
559 attributes.put(detail.getName(), toStringConverter.convert(value));
560 }
561 }
562 attributes.put(ATT_CONSUMER, "db");
563 appendStartTag(EL_GENERATE, attributes, writer, false);
564 writer.append('\n');
565
566 for (ComponentDescriptor cd : type.getComponents()) {
567 addAttribute(cd, writer);
568 }
569 writer.append(TAB);
570 appendEndElement(EL_GENERATE, writer);
571 writer.append("\n\n").append(TAB);
572 }
573
574 private void addAttribute(ComponentDescriptor component, LFNormalizingStringBuilder writer) {
575
576 boolean nullable = (component.isNullable() == null || component.isNullable());
577 if (component.getMaxCount() != null && component.getMaxCount().evaluate(null) == 1) {
578 component.setMaxCount(null);
579 }
580 if (component.getMinCount() != null && component.getMinCount().evaluate(null) == 1) {
581 component.setMinCount(null);
582 }
583 if (nullable) {
584 component.setNullable(null);
585 }
586
587 String elementName = null;
588 if (component instanceof PartDescriptor) {
589 elementName = EL_ATTRIBUTE;
590 } else if (component instanceof ReferenceDescriptor) {
591 elementName = EL_REFERENCE;
592 } else if (component instanceof IdDescriptor) {
593 elementName = EL_ID;
594 } else {
595 throw new UnsupportedOperationException("Component descriptor type not supported: " +
596 component.getClass().getSimpleName());
597 }
598
599 Map<String, String> attributes = new OrderedMap<>();
600 attributes.put(ATT_NAME, component.getName());
601 SimpleTypeDescriptorcom/rapiddweller/model/data/SimpleTypeDescriptor.html#SimpleTypeDescriptor">SimpleTypeDescriptor type = (SimpleTypeDescriptor) (component.getType() != null ?
602 dataModel.getTypeDescriptor(component.getType()) :
603 component.getLocalType());
604 if (type != null) {
605 for (FeatureDetail<?> detail : type.getDetails()) {
606 format(detail, attributes);
607 }
608 }
609 for (FeatureDetail<?> detail : component.getDetails()) {
610 format(detail, attributes);
611 }
612 if (nullable) {
613 attributes.put("nullQuota", "1");
614 }
615
616 writer.append(TAB).append(TAB).append("<!--").append(elementName);
617 for (Map.Entry<String, String> entry : attributes.entrySet()) {
618 writer.append(' ').append(entry.getKey()).append("=\"").append(entry.getValue()).append('"');
619 }
620 writer.append(" /-->");
621 writer.append('\n');
622 }
623
624 private String replaceVariables(String text) {
625 int varStart = 0;
626 Context context = new DefaultContext();
627 context.set("setup", setup);
628 context.set("version", VersionInfo.getInfo("benerator"));
629 while ((varStart = text.indexOf("${", varStart)) >= 0) {
630 int varEnd = text.indexOf("}", varStart);
631 if (varEnd < 0) {
632 throw new ConfigurationError("'${' without '}'");
633 }
634 String template = text.substring(varStart, varEnd + 1);
635 String path = template.substring(2, template.length() - 1).trim();
636 GraphAccessor accessor = new GraphAccessor(path);
637 Object varValue = accessor.getValue(context);
638 String varString = toStringConverter.convert(varValue);
639 if (!StringUtil.isEmpty(varString)) {
640 text = text.replace(template, varString);
641 }
642 varStart = varEnd;
643 }
644 text = text.replace("\n defaultEncoding=\"\"", "");
645 text = text.replace("\n defaultDataset=\"\"", "");
646 text = text.replace("\n defaultLocale=\"\"", "");
647 text = text.replace("\n defaultLineSeparator=\"\"", "");
648 return text;
649 }
650
651 }