View Javadoc
1   package guru.mikelue.jdut.yaml.node;
2   
3   import java.sql.Connection;
4   import java.util.Collections;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.Optional;
8   import java.util.stream.Collectors;
9   
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  import org.apache.commons.lang3.Validate;
13  import org.apache.commons.lang3.builder.EqualsBuilder;
14  import org.apache.commons.lang3.builder.HashCodeBuilder;
15  
16  import guru.mikelue.jdut.ConductorConfig;
17  import guru.mikelue.jdut.DataConductException;
18  import guru.mikelue.jdut.DataConductor;
19  import guru.mikelue.jdut.DuetFunctions;
20  import guru.mikelue.jdut.datagrain.DataField;
21  import guru.mikelue.jdut.datagrain.DataGrain;
22  import guru.mikelue.jdut.decorate.DataGrainDecorator;
23  import guru.mikelue.jdut.jdbc.JdbcFunction;
24  import guru.mikelue.jdut.operation.DataGrainOperator;
25  import guru.mikelue.jdut.yaml.LoadingYamlException;
26  
27  /**
28   * Represents the node of table and conveting internal structure to
29   * {@link DuetFunctions} object.
30   */
31  public class TableNode implements NodeBase {
32  	/**
33  	 * As the name of table.
34  	 */
35  	public final static class TableName {
36  		private final String name;
37  
38  		public TableName(String newName)
39  		{
40  			name = newName;
41  		}
42  
43  		@Override
44  		public int hashCode()
45  		{
46  			return new HashCodeBuilder(996547137, 439619991)
47  				.append(name)
48  			.toHashCode();
49  		}
50  
51  		@Override
52  		public boolean equals(Object obj)
53  		{
54  			if (obj == null) { return false; }
55  			if (obj == this) { return true; }
56  			if (obj.getClass() != getClass()) {
57  				return false;
58  			}
59  
60  			TableName rhs = (TableName)obj;
61  			return new EqualsBuilder()
62  				.append(this.name, rhs.name)
63  				.isEquals();
64  		}
65  	}
66  
67  	private final static class Config {
68  		private Config() {}
69  
70  		private Optional<String> buildOperation = Optional.empty();
71  		private Optional<String> cleanOperation = Optional.empty();
72  	}
73  
74  	private Logger logger = LoggerFactory.getLogger(TableNode.class);
75  	private List<?> dataRows = Collections.emptyList();
76  	private List<String> columns = Collections.emptyList();
77  	private List<String> keys = Collections.emptyList();
78  	private Optional<String> decorator = Optional.empty();
79  	private Config config = new Config();
80  	private TableName tableName;
81  
82  	@SuppressWarnings("unchecked")
83  	public TableNode(Object tableNode)
84  	{
85  		Map<TableName, ?> unknownTable = ((Map<TableName, ?>)tableNode);
86  
87  		tableName = unknownTable.keySet().stream().findFirst().get();
88  		setData(unknownTable.get(tableName));
89  	}
90  
91  	/**
92  	 * Converts this node to {@link DuetFunctions}.
93  	 *
94  	 * @param dataConductor the data conductor
95  	 * @param conductorConfig the configuration of conductor
96  	 * @param configNode The node of configuration
97  	 *
98  	 * @return The functions to be used by conductor
99  	 */
100 	@SuppressWarnings("unchecked")
101 	public DuetFunctions toDuetFunctions(
102 		DataConductor dataConductor, ConductorConfig conductorConfig,
103 		ConfigNode configNode
104 	) {
105 		DataGrain dataGrain = DataGrain.build(
106 			tableBuilder -> tableBuilder
107 				.name(tableName.name)
108 				.keys(keys.toArray(new String[0])),
109 			dataBuilder -> {
110 				dataBuilder.implicitColumns(columns.toArray(new String[0]));
111 
112 				dataRows.forEach(
113 					row -> {
114 						if (List.class.isInstance(row)) {
115 							dataBuilder.addValues(((List<Object>)row).toArray(new Object[0]));
116 							return;
117 						}
118 						if (Map.class.isInstance(row)) {
119 							dataBuilder.addFields(
120 								((Map<String, Object>)row).entrySet().stream()
121 									.map(entry -> dataBuilder.newField(entry.getKey(), entry.getValue()))
122 									.collect(Collectors.toList())
123 									.toArray(new DataField<?>[0])
124 							);
125 							return;
126 						}
127 
128 						throw new LoadingYamlException("Unknown type of \"data\"[%s] for table: %s", row.getClass(), tableName.name);
129 					}
130 				);
131 			}
132 		);
133 
134 		String nameOfBuilding = config.buildOperation.orElseGet(() -> configNode.getNameOfBuildOperator());
135 		String nameOfCleaning = config.cleanOperation.orElseGet(() -> configNode.getNameOfCleanOperator());
136 
137 		logger.debug(
138 			"Use [{}] for building, [{}] for cleaning, Table: [{}]",
139 			nameOfBuilding, nameOfCleaning, tableName.name
140 		);
141 
142 		/**
143 		 * Chaining the decorators
144 		 */
145 		DataGrainDecorator decoratorObject = rowBuilder -> {};
146 		if (configNode.getDecorator().isPresent()) {
147 			decoratorObject = decoratorObject.chain(
148 				conductorConfig.getDecorator(configNode.getDecorator().get())
149 					.orElseThrow(
150 						() -> new LoadingYamlException("Cannot found decorator: \"%s\"", configNode.getDecorator().get())
151 					)
152 			);
153 		}
154 		if (decorator.isPresent()) {
155 			decoratorObject = decoratorObject.chain(
156 				conductorConfig.getDecorator(decorator.get())
157 					.orElseThrow(
158 						() -> new LoadingYamlException("Cannot found decorator: \"%s\"", configNode.getDecorator().get())
159 					)
160 			);
161 		}
162 		// :~)
163 
164 		return new DuetFunctionsImplOfDataGrain(
165 			dataConductor,
166 			dataConductor.buildJdbcFunction(
167 				dataGrain.decorate(decoratorObject),
168 				conductorConfig.getOperator(nameOfBuilding).orElseThrow(
169 					() -> new LoadingYamlException("Cannot found operator: \"%s\"", nameOfBuilding)
170 				)
171 			),
172 			conductorConfig.getOperator(nameOfCleaning).orElseThrow(
173 				() -> new LoadingYamlException("Cannot found operator: \"%s\"", nameOfBuilding)
174 			)
175 		);
176 	}
177 
178 	@Override
179 	public NodeType getNodeType()
180 	{
181 		return NodeType.Table;
182 	}
183 
184 	@SuppressWarnings("unchecked")
185 	private void setData(Object unknownData)
186 	{
187 		logger.trace("Load table: {}", tableName.name);
188 
189 		/**
190 		 * Simple data configuration
191 		 */
192 		if (List.class.isInstance(unknownData)) {
193 			dataRows = (List<?>)unknownData;
194 			return;
195 		}
196 		// :~)
197 
198 		Map<String, ?> complexData = (Map<String, ?>)unknownData;
199 		complexData.forEach(
200 			(key, value) -> {
201 				switch (key) {
202 					case "columns":
203 						Validate.isTrue(List.class.isInstance(value), "\"columns\" need to be !!seq");
204 						columns = (List<String>)value;
205 						break;
206 					case "data":
207 						Validate.isTrue(List.class.isInstance(value), "\"data\" need to be !!seq");
208 						dataRows = (List<?>)value;
209 						break;
210 					case "keys":
211 						Validate.isTrue(List.class.isInstance(value), "\"keys\" need to be !!seq");
212 						keys = (List<String>)value;
213 						break;
214 					case "config":
215 						Validate.isTrue(Map.class.isInstance(value), "\"config\" need to be !!map");
216 						((Map<String, String>)value).forEach(
217 							(keyOfConfig, valueOfConfig) -> {
218 								switch (keyOfConfig) {
219 									case "build_operation":
220 										config.buildOperation = Optional.of(valueOfConfig);
221 										break;
222 									case "clean_operation":
223 										config.cleanOperation = Optional.of(valueOfConfig);
224 										break;
225 									case "decorator":
226 										decorator = Optional.of((String)valueOfConfig);
227 										break;
228 									default:
229 										throw new LoadingYamlException("Unknown property[%s] of \"config\" in table: \"%s\"", keyOfConfig, tableName.name);
230 								}
231 							}
232 						);
233 						break;
234 					default:
235 						throw new LoadingYamlException("Unknown property of \"%s\" in table: \"%s\"", key, tableName.name);
236 				}
237 			}
238 		);
239 	}
240 }
241 
242 class DuetFunctionsImplOfDataGrain implements DuetFunctions {
243 	private DataGrain cleanDataGrain;
244 
245 	private JdbcFunction<Connection, DataGrain> buildFunction;
246 	private JdbcFunction<Connection, DataGrain> cleanFunction;
247 
248 	DuetFunctionsImplOfDataGrain(
249 		DataConductor dataConductor,
250 		JdbcFunction<Connection, DataGrain> newBuildFunction,
251 		DataGrainOperator cleanOperator
252 	) {
253 		buildFunction = conn -> {
254 			DataGrain processedDataGrain = newBuildFunction.applyJdbc(conn)
255 				.reverse();
256 
257 			cleanDataGrain = processedDataGrain.reverse();
258 
259 			return processedDataGrain;
260 		};
261 
262 		cleanFunction = conn -> cleanOperator.operate(conn, cleanDataGrain);
263 	}
264 
265 	@Override
266 	public JdbcFunction<Connection, ?> getBuildFunction()
267 	{
268 		return buildFunction;
269 	}
270 
271 	@Override
272 	public JdbcFunction<Connection, ?> getCleanFunction()
273 	{
274 		if (cleanDataGrain == null) {
275 			throw new DataConductException("The data grain has not been used for building before used for cleaning");
276 		}
277 
278 		return cleanFunction;
279 	}
280 }