View Javadoc
1   package fr.ifremer.quadrige3.synchro.service.doc;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 Synchro Core
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.collect.ImmutableList;
27  import com.google.common.collect.Lists;
28  import com.google.common.collect.Sets;
29  import fr.ifremer.common.synchro.config.SynchroConfiguration;
30  import fr.ifremer.common.synchro.meta.SynchroDatabaseMetadata;
31  import fr.ifremer.common.synchro.meta.SynchroJoinMetadata;
32  import fr.ifremer.common.synchro.meta.SynchroTableMetadata;
33  import fr.ifremer.quadrige3.core.dao.technical.Assert;
34  import fr.ifremer.quadrige3.core.dao.technical.Daos;
35  import fr.ifremer.quadrige3.core.dao.technical.gson.Gsons;
36  import fr.ifremer.quadrige3.synchro.meta.DatabaseColumns;
37  import fr.ifremer.quadrige3.synchro.meta.data.DataSynchroTables;
38  import fr.ifremer.quadrige3.synchro.meta.referential.ReferentialSynchroTables;
39  import fr.ifremer.quadrige3.synchro.meta.system.RuleSynchroTables;
40  import fr.ifremer.quadrige3.synchro.service.SynchroDirection;
41  import fr.ifremer.quadrige3.synchro.service.data.DataSynchroContext;
42  import fr.ifremer.quadrige3.synchro.service.data.DataSynchroDatabaseConfiguration;
43  import fr.ifremer.quadrige3.synchro.service.referential.ReferentialSynchroContext;
44  import fr.ifremer.quadrige3.synchro.service.referential.ReferentialSynchroDatabaseConfiguration;
45  import fr.ifremer.quadrige3.synchro.vo.SynchroTableMetaVO;
46  import fr.ifremer.quadrige3.synchro.vo.SynchroTableRelationsVO;
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  import org.nuiton.i18n.I18n;
50  import org.springframework.beans.factory.annotation.Autowired;
51  import org.springframework.context.annotation.Lazy;
52  import org.springframework.stereotype.Service;
53  
54  import java.io.File;
55  import java.sql.Connection;
56  import java.sql.SQLException;
57  import java.util.*;
58  
59  /**
60   * Created by Ludovic on 07/12/2016.
61   */
62  @Service("docSynchroService")
63  @Lazy
64  public class DocSynchroServiceImpl implements DocSynchroService {
65  
66  	private static final Log log = LogFactory.getLog(DocSynchroServiceImpl.class);
67  	private static final SynchroDirection DEFAULT_SYNCHRO_DIRECTION = SynchroDirection.IMPORT_SERVER2TEMP;
68  
69  	@Autowired
70  	public DocSynchroServiceImpl(SynchroConfiguration config) {
71  	}
72  
73  	@Override
74  	public void createTableRelationsFile(Properties connectionProperties, File tableRelationFileCache) throws SQLException {
75  		createTableRelationsFile(connectionProperties, tableRelationFileCache, DEFAULT_SYNCHRO_DIRECTION);
76  	}
77  
78  	@Override
79  	public void createTableRelationsFile(Properties connectionProperties, File tableRelationFileCache, SynchroDirection synchroDirection)
80  			throws SQLException {
81  
82  		Assert.isTrue(Daos.isValidConnectionProperties(connectionProperties));
83  		Assert.notNull(tableRelationFileCache);
84  		Assert.notNull(synchroDirection);
85  
86  		log.info(I18n.t("quadrige3.synchro.doc.createTableRelations", tableRelationFileCache.getPath()));
87  
88  		Connection connection = Daos.createConnection(connectionProperties);
89  
90  		// table names to read
91  		Set<String> tableNames = Sets.newHashSet(ReferentialSynchroTables.tableNames());
92  		// add rules tables
93          tableNames.addAll(RuleSynchroTables.tableNames());
94  
95  		// REFERENTIAL
96  		ReferentialSynchroDatabaseConfiguration referentialConfiguration = new ReferentialSynchroDatabaseConfiguration(
97  				new ReferentialSynchroContext(synchroDirection, null), connectionProperties, false);
98  		referentialConfiguration.setFullMetadataEnable(true);
99  		referentialConfiguration.setColumnUpdateDate(DatabaseColumns.UPDATE_DT.name().toLowerCase());
100 
101 		// exclude unused columns todo depend de direction
102 		referentialConfiguration.excludeUnusedColumns();
103 
104 		// read metadata
105 		SynchroDatabaseMetadata referentialMetadata = new SynchroDatabaseMetadata(connection, referentialConfiguration);
106 		referentialMetadata.prepare(tableNames);
107 		referentialMetadata.close();
108 
109 		// DATA
110 		DataSynchroDatabaseConfiguration dataConfiguration = new DataSynchroDatabaseConfiguration(
111 				new DataSynchroContext(synchroDirection, null), connectionProperties, false);
112 		dataConfiguration.setFullMetadataEnable(true);
113 		dataConfiguration.setColumnRemoteId(DatabaseColumns.REMOTE_ID.name().toLowerCase());
114 		dataConfiguration.setColumnSynchronizationStatus(DatabaseColumns.SYNCHRONIZATION_STATUS.name().toLowerCase());
115 
116 		// exclude unused columns todo depend de direction
117 		dataConfiguration.excludeMeasurementUnusedColumns();
118 
119 		// read metadata for referential + data tables to get all relations
120 		tableNames.addAll(DataSynchroTables.getImportTablesIncludes());
121 		SynchroDatabaseMetadata dataMetadata = new SynchroDatabaseMetadata(connection, dataConfiguration);
122 		dataMetadata.prepare(tableNames);
123 		dataMetadata.close();
124 
125 		// close connection
126 		Daos.closeSilently(connection);
127 
128 		// Build tableRelations
129 		SynchroTableRelationsVO tableRelations = new SynchroTableRelationsVO();
130 
131 		for (String tableName : referentialMetadata.getLoadedTableNames()) {
132 			populateTableRelations(tableRelations, synchroDirection, referentialMetadata.getLoadedTable(tableName), false);
133 		}
134 
135 		for (String tableName : dataMetadata.getLoadedTableNames()) {
136 			populateTableRelations(tableRelations, synchroDirection, dataMetadata.getLoadedTable(tableName), true);
137 		}
138 
139 		// add custom relations
140 		addMoreRelations(tableRelations, synchroDirection);
141 
142 		// write file
143 		Gsons.serializeToFile(tableRelations, tableRelationFileCache);
144 	}
145 
146 	protected void addMoreRelations(SynchroTableRelationsVO tableRelations, SynchroDirection synchroDirection) {
147 		// do nothing by default
148 	}
149 
150 	protected boolean isJoinValid(SynchroJoinMetadata join, SynchroDirection synchroDirection) {
151 		return join.isValid();
152 	}
153 
154 	private void populateTableRelations(SynchroTableRelationsVO tableRelations, SynchroDirection synchroDirection, SynchroTableMetadata table,
155 			boolean isData) {
156 		populateTableRelations(tableRelations, synchroDirection, table, isData, Collections.emptyList());
157 	}
158 
159 	private void populateTableRelations(SynchroTableRelationsVO tableRelations, SynchroDirection synchroDirection, SynchroTableMetadata table,
160 			boolean isData, Collection<String> tablesNamesToIgnore) {
161 
162 		SynchroTableMetaVO tableMeta = tableRelations.getOrCreateTable(table.getName(), table.isRoot(), table.isWithUpdateDateColumn(), isData);
163 
164 		if (log.isDebugEnabled()) {
165 			log.debug(String.format("populateTableRelations in %s for %s", isData ? "DATA" : "REFERENTIAL", tableMeta.toExtendedString()));
166 		}
167 
168 		if (tablesNamesToIgnore.contains(table.getName())) {
169 			if (log.isDebugEnabled()) {
170 				log.debug(String.format("table %s has been ignored", tableMeta.toExtendedString()));
171 			}
172 			return;
173 		}
174 
175 		for (SynchroJoinMetadata join : table.getChildJoins()) {
176 			SynchroTableMetadata joinTable = join.getTargetTable();
177 
178 			boolean joinValid = isJoinValid(join, synchroDirection);
179 			if (!joinValid
180 					|| tableRelations.exists(joinTable.getName(), !isData)
181 					|| table.getName().equalsIgnoreCase(joinTable.getName())
182 					|| tablesNamesToIgnore.contains(joinTable.getName())) {
183 				if (log.isDebugEnabled()) {
184 					if (!joinValid) {
185 						log.debug(String.format("join from %s to %s is not valid", tableMeta.toExtendedString(), joinTable.getName()));
186 					}
187 					if (tableRelations.exists(joinTable.getName(), !isData)) {
188 						log.debug(String.format("joined table %s already exists in referential", joinTable.getName()));
189 					}
190 					if (table.getName().equalsIgnoreCase(joinTable.getName())) {
191 						log.debug(String.format("join from %s to %s is same table", tableMeta.toExtendedString(), joinTable.getName()));
192 					}
193 					if (tablesNamesToIgnore.contains(joinTable.getName())) {
194 						log.debug(String.format("joined table %s has been ignored", joinTable.getName()));
195 					}
196 				}
197 				continue;
198 			}
199 
200 			SynchroTableMetaVO joinTableMeta = tableRelations.getOrCreateTable(joinTable.getName(), joinTable.isRoot(),
201 					joinTable.isWithUpdateDateColumn(), isData);
202 			tableRelations.addChildTable(tableMeta, joinTableMeta);
203 			if (log.isDebugEnabled()) {
204 				log.debug(String.format("table %s is child of %s", joinTableMeta.toExtendedString(), tableMeta.toExtendedString()));
205 			}
206 		}
207 	}
208 
209 	@Override
210 	public List<SynchroTableMetaVO> getAffectedTablesForUpdateHierarchy(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO table) {
211 		List<SynchroTableMetaVO> result = Lists.newArrayList();
212 		SynchroTableMetaVO tableCopy = new SynchroTableMetaVO(table);
213 
214 		for (SynchroTableMetaVO rootTable : getFirstRootParentTables(tableRelations, tableCopy)) {
215 			result.add(populateRelationTables(tableRelations, rootTable, tableCopy).setToUpdate(true));
216 		}
217 
218 		return result;
219 	}
220 
221 	@Override
222 	public List<SynchroTableMetaVO> getAffectedTablesForDeleteHierarchy(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO table) {
223 		List<SynchroTableMetaVO> result = Lists.newArrayList();
224 		SynchroTableMetaVO tableCopy = new SynchroTableMetaVO(table);
225 
226 		// process parents
227 		for (SynchroTableMetaVO rootTable : getFirstRootParentTables(tableRelations, tableCopy)) {
228 			result.add(populateRelationTables(tableRelations, rootTable, tableCopy).setToUpdate(true));
229 		}
230 
231 		// process children on root table
232 		if (table.isRoot()) {
233 
234 			// first pass on root children
235 			for (SynchroTableMetaVO child : tableRelations.getChildTables(table)) {
236 				if (child.isRoot() && (table.isData() != child.isData())) {
237 					SynchroTableMetaVO childCopy = new SynchroTableMetaVO(child);
238 					childCopy.addChildren(tableCopy);
239 					result.add(childCopy.setToUpdate(true));
240 				}
241 			}
242 
243 			// second pass on non-root children
244 			for (SynchroTableMetaVO child : tableRelations.getChildTables(table)) {
245 				if (!child.isRoot() && (table.isData() != child.isData())) {
246 					for (SynchroTableMetaVO firstRootParent : getFirstRootParentTables(tableRelations, child, false)) {
247 						if (!result.contains(firstRootParent) && !table.equals(firstRootParent)) {
248 							result.add(populateRelationTables(tableRelations, firstRootParent, tableCopy).setToUpdate(true));
249 						}
250 					}
251 				}
252 			}
253 
254 		} else {
255 
256 			// process children on non root table
257 			for (SynchroTableMetaVO itsParent : getFirstRootParentTables(tableRelations, table, false)) {
258 				if (!result.contains(itsParent) && !table.equals(itsParent)) {
259 					result.add(populateRelationTables(tableRelations, itsParent, tableCopy).setToUpdate(true));
260 				}
261 			}
262 
263 		}
264 
265 		return result;
266 	}
267 
268 	protected List<SynchroTableMetaVO> getFirstRootParentTables(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO table) {
269 		return getFirstRootParentTables(tableRelations, table, false);
270 	}
271 
272 	protected List<SynchroTableMetaVO> getFirstRootParentTables(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO table,
273 			boolean dataTableOnly) {
274 		if (table.isRoot() && !table.isData()) {
275 			return Collections.emptyList();
276 		}
277 		Set<SynchroTableMetaVO> parents = Sets.newHashSet();
278 		for (SynchroTableMetaVO parentTable : findParentContainsChild(tableRelations, table)) {
279 			boolean dataOnly = dataTableOnly || table.isData();
280 
281 			if (parentTable.isRoot() && parentTable.isData() == dataOnly) {
282 				parents.add(parentTable);
283 			}
284 			if (dataOnly) {
285 				parents.addAll(getFirstRootParentTables(tableRelations, parentTable, true));
286 			}
287 		}
288 		List<SynchroTableMetaVO> result = Lists.newArrayList(parents);
289 		Collections.sort(result);
290 		return ImmutableList.copyOf(parents);
291 	}
292 
293 	private List<SynchroTableMetaVO> findParentContainsChild(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO targetChildTable) {
294 		List<SynchroTableMetaVO> result = Lists.newArrayList();
295 		for (SynchroTableMetaVO parentTable : tableRelations.getChildTableMap().keySet()) {
296 			if (tableRelations.getChildTables(parentTable).contains(targetChildTable)) {
297 				result.add(parentTable);
298 			}
299 		}
300 		return ImmutableList.copyOf(result);
301 	}
302 
303 	private SynchroTableMetaVO populateRelationTables(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO base, SynchroTableMetaVO target) {
304 		SynchroTableMetaVO result = new SynchroTableMetaVO(base);
305 		List<SynchroTableMetaVO> nextChildTables = getNextChildTable(tableRelations, base, target);
306 		if (!nextChildTables.isEmpty()) {
307 			for (SynchroTableMetaVO nextChild : nextChildTables) {
308 				if (nextChild.equals(target)) {
309 					result.addChildren(new SynchroTableMetaVO(nextChild));
310 				} else {
311 					result.addChildren(populateRelationTables(tableRelations, nextChild, target));
312 				}
313 			}
314 		} else {
315 			// get first parent
316 			for (SynchroTableMetaVO nextParent : getNextParentTable(tableRelations, base, target)) {
317 				if (nextParent.equals(target)) {
318 					result.addChildren(new SynchroTableMetaVO(nextParent));
319 				}
320 			}
321 		}
322 		return result;
323 	}
324 
325 	private List<SynchroTableMetaVO> getNextChildTable(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO base, SynchroTableMetaVO target) {
326 		List<SynchroTableMetaVO> result = Lists.newArrayList();
327 		// get children containing target
328 		for (SynchroTableMetaVO child : tableRelations.getChildTables(base)) {
329 			if (child.equals(target)) {
330 				result.add(child);
331 			} else if (findTableInChildren(tableRelations, child, target)) {
332 				// child relation
333 				result.add(child);
334 			} else if (findTableInParents(tableRelations, child, target)) {
335 				result.add(child);
336 			}
337 		}
338 		Collections.sort(result);
339 		return ImmutableList.copyOf(result);
340 	}
341 
342 	private List<SynchroTableMetaVO> getNextParentTable(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO base, SynchroTableMetaVO target) {
343 		List<SynchroTableMetaVO> result = Lists.newArrayList();
344 		// get parents containing target
345 		for (SynchroTableMetaVO parent : findParentContainsChild(tableRelations, base)) {
346 			if (parent.equals(target)) {
347 				result.add(parent);
348 			}
349 		}
350 		Collections.sort(result);
351 		return ImmutableList.copyOf(result);
352 	}
353 
354 	private boolean findTableInChildren(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO table, SynchroTableMetaVO target) {
355 		for (SynchroTableMetaVO child : tableRelations.getChildTables(table)) {
356 			if (child.equals(target)) {
357 				return true;
358 			}
359 		}
360 		for (SynchroTableMetaVO child : tableRelations.getChildTables(table)) {
361 			if (findTableInChildren(tableRelations, child, target)) {
362 				return true;
363 			}
364 			if (findTableInParents(tableRelations, child, target)) {
365 				return true;
366 			}
367 		}
368 		return false;
369 	}
370 
371 	private boolean findTableInParents(SynchroTableRelationsVO tableRelations, SynchroTableMetaVO table, SynchroTableMetaVO target) {
372 		for (SynchroTableMetaVO parent : findParentContainsChild(tableRelations, table)) {
373 			if (parent.equals(target)) {
374 				return true;
375 			}
376 		}
377 		return false;
378 	}
379 
380 	protected boolean notContains(List<SynchroTableMetaVO> tables, SynchroTableMetaVO tableToFind) {
381 		if (tables.contains(tableToFind)) {
382 			return false;
383 		}
384 
385 		for (SynchroTableMetaVO table : tables) {
386 			boolean notFound = notContains(table.getChildren(), tableToFind);
387 			if (!notFound) {
388 				return false;
389 			}
390 		}
391 
392 		return true;
393 	}
394 
395 }