1 package guru.mikelue.jdut.yaml;
2
3 import java.io.IOException;
4 import java.io.Reader;
5 import java.math.BigDecimal;
6 import java.sql.Blob;
7 import java.sql.Clob;
8 import java.sql.Connection;
9 import java.sql.JDBCType;
10 import java.sql.NClob;
11 import java.sql.SQLXML;
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.Date;
15 import java.util.List;
16 import java.util.Optional;
17 import java.util.function.Consumer;
18 import java.util.function.Supplier;
19 import javax.sql.DataSource;
20
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23 import org.yaml.snakeyaml.Yaml;
24 import org.yaml.snakeyaml.constructor.AbstractConstruct;
25 import org.yaml.snakeyaml.constructor.Construct;
26 import org.yaml.snakeyaml.constructor.Constructor;
27 import org.yaml.snakeyaml.nodes.Node;
28 import org.yaml.snakeyaml.nodes.Tag;
29
30 import guru.mikelue.jdut.ConductorConfig;
31 import guru.mikelue.jdut.ConductorContext;
32 import guru.mikelue.jdut.DataConductException;
33 import guru.mikelue.jdut.DataConductor;
34 import guru.mikelue.jdut.DuetConductor;
35 import guru.mikelue.jdut.DuetFunctions;
36 import guru.mikelue.jdut.jdbc.JdbcFunction;
37 import guru.mikelue.jdut.jdbc.JdbcSupplier;
38 import guru.mikelue.jdut.jdbc.JdbcTemplateFactory;
39 import guru.mikelue.jdut.jdbc.SQLExceptionConvert;
40 import guru.mikelue.jdut.jdbc.function.Transactional;
41 import guru.mikelue.jdut.operation.DefaultOperatorFactory;
42 import guru.mikelue.jdut.yaml.node.*;
43 import static guru.mikelue.jdut.yaml.YamlTags.*;
44
45
46
47
48 public class YamlConductorFactory {
49 private Logger logger = LoggerFactory.getLogger(YamlConductorFactory.class);
50 private DataConductor dataConductor;
51 private ConductorConfig conductorConfig;
52 private Constructor jdutConstructor;
53
54
55
56
57
58
59
60
61
62
63
64 public static YamlConductorFactory build(
65 DataSource dataSource
66 ) {
67 return build(dataSource, builder -> {});
68 }
69
70
71
72
73
74
75
76
77
78
79
80
81 public static YamlConductorFactory build(
82 DataSource dataSource,
83 Consumer<ConductorConfig.Builder> builderConsumer
84 ) {
85 final ConductorConfig config = ConductorConfig.build(builderConsumer);
86 ConductorConfig finalConfig = ConductorConfig.build(
87
88
89
90 builder -> {
91 if (!config.getResourceLoader().isPresent()) {
92 builder.resourceLoader(ReaderFunctions::currentThreadContext);
93 }
94 if (!config.getOperatorFactory().isPresent()) {
95 builder.operatorFactory(
96 DefaultOperatorFactory.build(dataSource, factoryBuilder -> {})
97 );
98 }
99 },
100
101 config
102 );
103
104 YamlConductorFactory newFactory = new YamlConductorFactory();
105 newFactory.conductorConfig = finalConfig;
106 newFactory.dataConductor = new DataConductor(dataSource);
107 newFactory.jdutConstructor = new JdutConstructor(finalConfig);
108
109 return newFactory;
110 }
111
112 private YamlConductorFactory() {}
113
114
115
116
117
118
119
120
121 public DuetConductor conductResource(
122 String yamlResourceName
123 ) {
124 return conductResource(yamlResourceName, builder -> {});
125 }
126
127
128
129
130
131
132
133
134 public DuetConductor conductResource(
135 String yamlResourceName,
136 Consumer<ConductorConfig.Builder> builderConsumer
137 ) {
138 final ConductorConfig finalConfig = buildConfig(builderConsumer);
139
140 try (
141 Reader yamlReader = finalConfig.getResourceLoader().get().apply(yamlResourceName)
142 ) {
143 return conductYaml(
144 yamlReader, builderConsumer
145 );
146 } catch (IOException e) {
147 throw new RuntimeException(e);
148 }
149 }
150
151
152
153
154
155
156
157
158
159
160 public DuetConductor conductYaml(
161 Reader yamlReader
162 ) {
163 return conductYaml(yamlReader, builder -> {});
164 }
165
166
167
168
169
170
171
172
173
174
175 public DuetConductor conductYaml(
176 Reader yamlReader,
177 Consumer<ConductorConfig.Builder> builderConsumer
178 ) {
179 final ConductorConfig finalConfig = buildConfig(builderConsumer);
180
181 List<DuetFunctions> operationsInAllDoc = new ArrayList<>(4);
182
183 Yaml yaml = new Yaml(jdutConstructor);
184 for (Object object: yaml.loadAll(yamlReader)) {
185 List<?> conductorDoc = (List<?>)object;
186 logger.trace("Got [{}] conduct elements.", conductorDoc.size());
187
188
189
190
191 ConfigNode configNode = new ConfigNode();
192
193 List<NodeBase> nodes = new ArrayList<>(8);
194 for (Object conductElement: conductorDoc) {
195 switch (NodeType.getNodeType(conductElement)) {
196 case Config:
197 configNode = new ConfigNode(conductElement);
198 break;
199 case Table:
200 nodes.add(new TableNode(conductElement));
201 break;
202 case Code:
203 nodes.add((CodeNode)conductElement);
204 break;
205 case Defines:
206
207 break;
208 default:
209 throw new LoadingYamlException("Unknown node: \"%s\" for conduction", conductElement);
210 }
211 }
212
213
214
215
216
217 final DuetFunctionsImpleOfDoc operationsInDoc =
218 new DuetFunctionsImpleOfDoc(finalConfig);
219 operationsInDoc.setTransactional(configNode.getTransactionl());
220 operationsInDoc.setTransactionIsolation(configNode.getTransactionIsolation());
221
222 final ConfigNode finalConfigNode = configNode;
223
224 nodes.forEach(
225 node -> { switch (node.getNodeType()) {
226 case Table:
227 operationsInDoc.add(
228 ((TableNode)node).toDuetFunctions(
229 dataConductor, finalConfig,
230 finalConfigNode
231 )
232 );
233 break;
234 case Code:
235 operationsInDoc.add(
236 ((CodeNode)node).toDuetFunctions()
237 );
238 break;
239 default:
240 throw new LoadingYamlException("Unknown node type: \"%s\"", node.getNodeType());
241 }}
242 );
243
244
245 operationsInAllDoc.add(operationsInDoc);
246 }
247
248 return new DuetConductorImplOfAssembly(dataConductor, operationsInAllDoc);
249 }
250
251 private ConductorConfig buildConfig(Consumer<ConductorConfig.Builder> builderConsumer)
252 {
253 return ConductorConfig.build(
254 builder -> {
255 builderConsumer.accept(builder);
256 builder.parent(conductorConfig);
257 }
258 );
259 }
260 }
261
262 class JdutConstructor extends Constructor {
263 private Logger logger = LoggerFactory.getLogger(YamlConductorFactory.class);
264
265 private final ConductorConfig conductorConfig;
266
267 private final Construct sqlConstruct = new SqlConstruct();
268 private final Construct jdutConstruct = new JdutConstruct();
269 private final Construct dbTypeConstruct = new DbTypeConstruct();
270
271 JdutConstructor(ConductorConfig newConfig)
272 {
273 conductorConfig = newConfig;
274 }
275
276 @Override
277 protected Construct getConstructor(Node node)
278 {
279 if (node.getTag().startsWith(NAMESPACE_SQL)) {
280 return sqlConstruct;
281 }
282 if (node.getTag().startsWith(NAMESPACE_JDUT)) {
283 return jdutConstruct;
284 }
285 if (node.getTag().startsWith(NAMESPACE_DB_TYPE)) {
286 return dbTypeConstruct;
287 }
288
289 return super.getConstructor(node);
290 }
291
292 private class DbTypeConstruct extends AbstractConstruct {
293 @Override
294 public Object construct(Node node)
295 {
296 String tagValue = node.getTag().getValue().replace(NAMESPACE_DB_TYPE, "")
297 .toUpperCase();
298
299 JDBCType targetType = JDBCType.valueOf(tagValue);
300
301 switch (targetType) {
302 case BINARY:
303 case BLOB:
304 case LONGVARBINARY:
305 case VARBINARY:
306 return makeBinary(targetType, (byte[])JdutConstructor.this.yamlConstructors.get(Tag.BINARY).construct(node));
307
308 case CHAR:
309 case LONGNVARCHAR:
310 case LONGVARCHAR:
311 case NCHAR:
312 case NVARCHAR:
313 case VARCHAR:
314 case CLOB:
315 case NCLOB:
316 case SQLXML:
317 return makeString(targetType, (String)JdutConstructor.this.yamlConstructors.get(Tag.STR).construct(node));
318
319 case BOOLEAN:
320 return (Boolean)JdutConstructor.this.yamlConstructors.get(Tag.BOOL).construct(node);
321
322 case BIT:
323 case BIGINT:
324 case INTEGER:
325 case SMALLINT:
326 case TINYINT:
327 return makeIntegerNumber(targetType, (Number)JdutConstructor.this.yamlConstructors.get(Tag.INT).construct(node));
328
329 case FLOAT:
330 case DOUBLE:
331 return makeDoubleNumber(targetType, (Double)JdutConstructor.this.yamlConstructors.get(Tag.FLOAT).construct(node));
332 case NUMERIC:
333 case DECIMAL:
334 case REAL:
335 return makeDecimalNumber(targetType, (String)JdutConstructor.this.yamlConstructors.get(Tag.STR).construct(node));
336
337
338
339
340
341
342
343
344
345
346
347
348 case NULL:
349 throw new LoadingYamlException("Just setting null");
350
351 case DATE:
352 case TIME:
353 case TIMESTAMP:
354 return makeTime(targetType, (Date)JdutConstructor.this.yamlConstructors.get(Tag.TIMESTAMP).construct(node));
355
356 default:
357 throw new LoadingYamlException("Cannot recgonize tag or not supported. Vaue: \"%s\", JDBCType: [%s]", tagValue, targetType);
358 }
359 }
360 private Object makeTime(JDBCType jdbcType, Date timeValue)
361 {
362 switch (jdbcType) {
363 case DATE:
364 return new java.sql.Date(timeValue.getTime());
365 case TIME:
366 return new java.sql.Time(timeValue.getTime());
367 case TIMESTAMP:
368 return timeValue;
369
370 default:
371 throw new LoadingYamlException("Unsupported time type: [%s]", jdbcType);
372 }
373 }
374 private Object makeBinary(JDBCType jdbcType, byte[] binaryData)
375 {
376 switch (jdbcType) {
377 case BINARY:
378 case LONGVARBINARY:
379 case VARBINARY:
380 return binaryData;
381
382 case BLOB:
383 return buildSupplier(
384 () -> {
385 Connection conn = getConnectionFromCondcutorContext();
386 Blob blob = conn.createBlob();
387 blob.setBytes(1, binaryData);
388 return blob;
389 }
390 );
391
392 default:
393 throw new LoadingYamlException("Unsupported binary type: [%s]", jdbcType);
394 }
395 }
396 private Object makeString(JDBCType jdbcType, String value)
397 {
398 switch (jdbcType) {
399 case CHAR:
400 case LONGNVARCHAR:
401 case LONGVARCHAR:
402 case NCHAR:
403 case NVARCHAR:
404 case VARCHAR:
405 return value;
406 case CLOB:
407 return buildSupplier(
408 () -> {
409 Connection conn = getConnectionFromCondcutorContext();
410 Clob clob = conn.createClob();
411 clob.setString(1, value);
412 return clob;
413 }
414 );
415 case NCLOB:
416 return buildSupplier(
417 () -> {
418 Connection conn = getConnectionFromCondcutorContext();
419 NClob nclob = conn.createNClob();
420 nclob.setString(1, value);
421 return nclob;
422 }
423 );
424 case SQLXML:
425 return buildSupplier(
426 () -> {
427 Connection conn = getConnectionFromCondcutorContext();
428 SQLXML sqlXml = conn.createSQLXML();
429 sqlXml.setString(value);
430 return sqlXml;
431 }
432 );
433 default:
434 throw new LoadingYamlException("Unsupported text type: [%s]", jdbcType);
435 }
436 }
437 private Object makeIntegerNumber(JDBCType jdbcType, Number integerValue)
438 {
439 switch (jdbcType) {
440 case BIT:
441 case TINYINT:
442 return integerValue.byteValue();
443 case SMALLINT:
444 return integerValue.shortValue();
445 case INTEGER:
446 return integerValue.intValue();
447 case BIGINT:
448 return integerValue.longValue();
449 default:
450 throw new LoadingYamlException("Unsupported integer type: [%s]", jdbcType);
451 }
452 }
453 private Object makeDoubleNumber(JDBCType jdbcType, Double doubleValue)
454 {
455 switch (jdbcType) {
456 case FLOAT:
457 return doubleValue.floatValue();
458 case DOUBLE:
459 return doubleValue;
460 default:
461 throw new LoadingYamlException("Unsupported double type: [%s]", jdbcType);
462 }
463 }
464 private Object makeDecimalNumber(JDBCType jdbcType, String decimalValue)
465 {
466 switch (jdbcType) {
467 case NUMERIC:
468 case DECIMAL:
469 case REAL:
470 return new BigDecimal(decimalValue);
471
472 default:
473 throw new LoadingYamlException("Unsupported decimal type: [%s]", jdbcType);
474 }
475 }
476
477 private <T> Supplier<T> buildSupplier(JdbcSupplier<T> jdbcSupplier)
478 {
479 return jdbcSupplier.asSupplier(
480 conductorConfig.getSqlExceptionConvert().orElse(SQLExceptionConvert::runtimeException)
481 );
482 }
483
484 private Connection getConnectionFromCondcutorContext()
485 {
486 return ConductorContext.getCurrentConnection()
487 .orElseThrow(() -> new DataConductException("The conductor did not initialize a thread local connection"));
488 }
489 }
490
491 private class JdutConstruct extends AbstractConstruct {
492 private JdutConstruct() {}
493
494 @Override
495 public Object construct(Node node)
496 {
497 String tagValue = node.getTag().getValue().replace(NAMESPACE_JDUT, "");
498
499 switch (tagValue) {
500 case "supplier":
501 String supplierName =
502 (String)JdutConstructor.this.yamlConstructors.get(Tag.STR).construct(node);
503
504 logger.trace("Load supplier: \"{}\"", supplierName);
505 return conductorConfig.getSupplier(supplierName).orElseThrow(
506 () -> new LoadingYamlException("Cannot found supplier: \"%s\"", supplierName)
507 );
508 default:
509 throw new LoadingYamlException("Cannot recgonize tag: \"%s\"", tagValue);
510 }
511 }
512 }
513
514 private class SqlConstruct extends AbstractConstruct {
515 private SqlConstruct() {}
516
517 @Override
518 public Object construct(Node node)
519 {
520 String tagValue = node.getTag().getValue().replace(NAMESPACE_SQL, "");
521
522 switch (tagValue) {
523 case "table":
524 return new TableNode.TableName(
525 (String)JdutConstructor.this.yamlConstructors.get(Tag.STR).construct(node)
526 );
527 case "code":
528 return new CodeNode(
529 JdutConstructor.this.yamlConstructors.get(Tag.MAP).construct(node)
530 );
531 case "statement":
532 String sql = (String)JdutConstructor.this.yamlConstructors.get(Tag.STR).construct(node);
533 JdbcFunction<Connection, ?> statementFunc = conn -> JdbcTemplateFactory.buildSupplier(
534 () -> conn.createStatement(),
535 stat -> {
536 logger.trace("Execute statement: [{}]", sql);
537 return stat.executeUpdate(sql);
538 }
539 ).getJdbc();
540
541 return statementFunc;
542 case "jdbcfunction":
543 String functionName = (String)JdutConstructor.this.yamlConstructors.get(Tag.STR).construct(node);
544 logger.trace("Load JDBC function: \"{}\"", functionName);
545 return conductorConfig.getJdbcFunction(functionName).orElseThrow(
546 () -> new LoadingYamlException("Cannot found JDBC function: \"%s\"", functionName)
547 );
548 default:
549 throw new LoadingYamlException("Cannot recgonize tag: \"%s\"", tagValue);
550 }
551 }
552 }
553 }
554
555 class DuetFunctionsImpleOfDoc implements DuetFunctions {
556 private Boolean transactional = null;
557 private Optional<Integer> transactionIsolation = null;
558 private Optional<SQLExceptionConvert<?>> sqlExceptionConvert = null;
559
560 final List<DuetFunctions> buildFunctions = new ArrayList<>(4);
561
562 DuetFunctionsImpleOfDoc(ConductorConfig conductorConfig)
563 {
564 sqlExceptionConvert = conductorConfig.getSqlExceptionConvert();
565 }
566
567 void add(DuetFunctions duetFunctions)
568 {
569 buildFunctions.add(duetFunctions);
570 }
571
572 void setTransactional(boolean flag)
573 {
574 transactional = flag;
575 }
576 void setTransactionIsolation(Optional<Integer> newTransactionIsolation)
577 {
578 transactionIsolation = newTransactionIsolation;
579 }
580
581 @Override
582 public JdbcFunction<Connection, ?> getBuildFunction()
583 {
584 JdbcFunction<Connection, ?> mainFunction = conn -> {
585 buildFunctions.forEach(duetFunc -> duetFunc.getBuildFunction()
586 .asFunction(sqlExceptionConvert.orElse(SQLExceptionConvert::runtimeException))
587 .apply(conn)
588 );
589 return buildFunctions.size();
590 };
591
592 return wrapTransactionIfPresent(mainFunction);
593 }
594
595 @Override
596 public JdbcFunction<Connection, ?> getCleanFunction()
597 {
598 final List<DuetFunctions> cleanFunctions = new ArrayList<>(buildFunctions);
599 Collections.reverse(cleanFunctions);
600
601 JdbcFunction<Connection, ?> mainFunction = conn -> {
602 cleanFunctions.forEach(duetFunc -> duetFunc.getCleanFunction()
603 .asFunction(sqlExceptionConvert.orElse(SQLExceptionConvert::runtimeException))
604 .apply(conn)
605 );
606 return cleanFunctions.size();
607 };
608
609 return wrapTransactionIfPresent(mainFunction);
610 }
611
612 private JdbcFunction<Connection, ?> wrapTransactionIfPresent(JdbcFunction<Connection, ?> ordinaryFunction)
613 {
614 if (!transactional) {
615 return ordinaryFunction;
616 }
617
618 if (transactionIsolation.isPresent()) {
619 return ordinaryFunction.surroundedBy(new Transactional<>(transactionIsolation.get()));
620 } else {
621 return ordinaryFunction.surroundedBy(Transactional::simple);
622 }
623 }
624 }
625
626 class DuetConductorImplOfAssembly implements DuetConductor {
627 private final DataConductor dataConductor;
628
629 final List<DuetFunctions> buildFunctions;
630 final List<DuetFunctions> cleanFunctions;
631
632 DuetConductorImplOfAssembly(DataConductor newDataConductor, List<DuetFunctions> newFunctions)
633 {
634 buildFunctions = newFunctions;
635
636 List<DuetFunctions> reversedFunctions = new ArrayList<>(buildFunctions);
637 Collections.reverse(reversedFunctions);
638 cleanFunctions = reversedFunctions;
639
640 dataConductor = newDataConductor;
641 }
642
643 @Override
644 public void build()
645 {
646 dataConductor.conduct(
647 conn -> {
648 buildFunctions.forEach(duetFunc -> duetFunc.getBuildFunction().asFunction().apply(conn));
649 return buildFunctions.size();
650 }
651 );
652 }
653
654 @Override
655 public void clean()
656 {
657 dataConductor.conduct(
658 conn -> {
659 cleanFunctions.forEach(duetFunc -> duetFunc.getCleanFunction().asFunction().apply(conn));
660 return cleanFunctions.size();
661 }
662 );
663 }
664 }