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