View Javadoc
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   * The factory used to build {@link DuetConductor} by data definition of YAML format.
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       * Builds factory by {@link DataSource}.<br>
56  	 *
57  	 * By default, this method would build resource loader as {@link ReaderFunctions#currentThreadContext} and
58  	 * {@link DefaultOperatorFactory} as operator factory.
59       *
60       * @param dataSource The data source for target database
61  	 *
62  	 * @return The initialized factory
63       */
64      public static YamlConductorFactory build(
65          DataSource dataSource
66      ) {
67  		return build(dataSource, builder -> {});
68      }
69  
70      /**
71       * Builds factory by {@link DataSource}.<br>
72  	 *
73  	 * By default, this method would build resource loader as {@link ReaderFunctions#currentThreadContext} and
74  	 * {@link DefaultOperatorFactory} as operator factory.
75       *
76  	 * @param dataSource The data source for target database
77       * @param builderConsumer consumer used to set-up this factory
78  	 *
79  	 * @return The initialized factory
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  			 * Initializes default value
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      * Builds conductor by resource of YAML.
116      *
117      * @param yamlResourceName The name of resource
118 	 *
119 	 * @return The conductor for building and cleaning data
120      */
121     public DuetConductor conductResource(
122         String yamlResourceName
123     ) {
124         return conductResource(yamlResourceName, builder -> {});
125     }
126     /**
127      * Builds conductor by resource of YAML.
128      *
129      * @param yamlResourceName The name of resource
130      * @param builderConsumer The configuration builder
131 	 *
132 	 * @return The conductor for building and cleaning data
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      * Builds conductor by reader of YAML.<br>
153 	 *
154 	 * This method won't close the reader object.
155      *
156      * @param yamlReader The reader of yaml content
157 	 *
158 	 * @return The conductor for building and cleaning data
159      */
160     public DuetConductor conductYaml(
161         Reader yamlReader
162     ) {
163         return conductYaml(yamlReader, builder -> {});
164     }
165     /**
166      * Builds conductor by reader of YAML.
167 	 *
168 	 * This method won't close the reader object.
169      *
170      * @param yamlReader The reader of yaml content
171      * @param builderConsumer The configuration builder
172 	 *
173 	 * @return The conductor for building and cleaning data
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 			 * Loads nodes and config
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 						// Do nothing for defines
207 						break;
208 					default:
209 						throw new LoadingYamlException("Unknown node: \"%s\" for conduction", conductElement);
210 				}
211 			}
212 			// :~)
213 
214 			/**
215 			 * Builds building and cleaning functions for a document
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 				// Unsupported type
338 				//case ARRAY:
339 				//case DATALINK:
340 				//case DISTINCT:
341 				//case JAVA_OBJECT:
342 				//case REF:
343 				//case REF_CURSOR:
344 				//case ROWID:
345 				//case STRUCT:
346 				//case OTHER:
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 }