View Javadoc
1   package fr.ifremer.quadrige3.synchro.intercept.referential;
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.base.Joiner;
27  import com.google.common.collect.*;
28  import com.google.common.eventbus.Subscribe;
29  import fr.ifremer.common.synchro.intercept.SynchroInterceptorBase;
30  import fr.ifremer.common.synchro.meta.SynchroJoinMetadata;
31  import fr.ifremer.common.synchro.meta.SynchroMetadataUtils;
32  import fr.ifremer.common.synchro.meta.SynchroTableMetadata;
33  import fr.ifremer.common.synchro.meta.event.CreateQueryEvent;
34  import fr.ifremer.common.synchro.meta.event.LoadJoinEvent;
35  import fr.ifremer.common.synchro.meta.event.LoadTableEvent;
36  import fr.ifremer.common.synchro.query.SynchroQueryBuilder;
37  import fr.ifremer.common.synchro.query.SynchroQueryName;
38  import fr.ifremer.common.synchro.query.SynchroQueryOperator;
39  import fr.ifremer.quadrige3.core.dao.referential.StatusCode;
40  import fr.ifremer.quadrige3.synchro.intercept.referential.internal.ImportFromFileFkInterceptor;
41  import fr.ifremer.quadrige3.synchro.intercept.referential.internal.ImportFromFileNumericalPkInterceptor;
42  import fr.ifremer.quadrige3.synchro.meta.DatabaseColumns;
43  import fr.ifremer.quadrige3.synchro.meta.administration.MetaProgramSynchroTables;
44  import fr.ifremer.quadrige3.synchro.meta.referential.ReferentialSynchroTables;
45  import fr.ifremer.quadrige3.synchro.meta.system.ContextAndFilterSynchroTables;
46  import fr.ifremer.quadrige3.synchro.service.SynchroDirection;
47  import fr.ifremer.quadrige3.synchro.service.referential.ReferentialSynchroDatabaseConfiguration;
48  import org.apache.commons.collections4.CollectionUtils;
49  import org.apache.commons.lang3.StringUtils;
50  import org.apache.commons.logging.Log;
51  import org.apache.commons.logging.LogFactory;
52  
53  import java.util.Collection;
54  import java.util.Map;
55  import java.util.Set;
56  
57  /**
58   * Manage only data table with columns 'update_dt' and/or 'status_cd' and/or 'prog_cd'
59   * 
60   * @author Benoit Lavenier <benoit.lavenier@e-is.pro>
61   * @since 1.0
62   */
63  public class ReferentialTableInterceptor extends AbstractReferentialInterceptor {
64  
65  	private static final Log log = LogFactory.getLog(ReferentialTableInterceptor.class);
66  
67  	/** Constant <code>childJoinIncludes</code> */
68  	private static final Multimap<String, String> childJoinIncludes;
69  	/** Constant <code>childJoinExcludes</code> */
70  	private static final Multimap<String, String> childJoinExcludes;
71  	/** Constant <code>naturalIds</code> */
72  	private static final Multimap<String, String> naturalIds;
73  
74  	static {
75  		childJoinIncludes = initChildJoinIncludes();
76  		childJoinExcludes = initChildJoinExcludes();
77  		naturalIds = initNaturalIds();
78  	}
79  
80  	private String statusWhereClauseOrNull;
81  	private final Map<String, Map<String, Integer>> recursiveColumnsByTable = Maps.newHashMap();
82  
83  	/**
84  	 * <p>
85  	 * Constructor for ReferentialTableInterceptor.
86  	 * </p>
87  	 */
88  	public ReferentialTableInterceptor() {
89  		super(ReferentialSynchroTables.getImportTablesIncludes());
90  	}
91  
92  	/** {@inheritDoc} */
93  	@Override
94  	protected void init(ReferentialSynchroDatabaseConfiguration config) {
95  		statusWhereClauseOrNull = createStatusWhereClauseOrNull(config);
96  	}
97  
98  	/** {@inheritDoc} */
99  	@Override
100 	public SynchroInterceptorBase clone() {
101 		ReferentialTableInterceptor result = (ReferentialTableInterceptor) super.clone();
102 		result.statusWhereClauseOrNull = this.statusWhereClauseOrNull;
103 		return result;
104 	}
105 
106 	/**
107 	 * <p>
108 	 * handleQuery.
109 	 * </p>
110 	 * 
111 	 * @param e
112 	 *            a {@link fr.ifremer.common.synchro.meta.event.CreateQueryEvent} object.
113 	 */
114 	@Subscribe
115 	public void handleQuery(CreateQueryEvent e) {
116 
117 		switch (e.queryName) {
118 		case count:
119 		case countFromUpdateDate:
120 		case select:
121 		case selectFromUpdateDate:
122 		case selectMaxUpdateDate:
123 			// Add restriction
124 			e.sql = addRestriction(e.source, e.queryName, e.sql);
125 
126 		default:
127 			break;
128 		}
129 	}
130 
131 	/**
132 	 * <p>
133 	 * handleTableLoad.
134 	 * </p>
135 	 * 
136 	 * @param e
137 	 *            a {@link fr.ifremer.common.synchro.meta.event.LoadTableEvent} object.
138 	 */
139 	@Subscribe
140 	public void handleTableLoad(LoadTableEvent e) {
141 
142 		SynchroTableMetadata table = e.table;
143 		SynchroDirection direction = getConfig().getDirection();
144 
145 		boolean hasStatusFilter = statusWhereClauseOrNull != null;
146 		boolean hasUpdateDateColumn = hasColumns(table, DatabaseColumns.UPDATE_DT.name());
147 		boolean hasStatusCdColumn = hasColumns(table, DatabaseColumns.STATUS_CD.name());
148 		boolean isRoot = hasUpdateDateColumn
149 				&& (!hasStatusFilter || hasStatusCdColumn);
150 
151 		// Set isRoot :
152 		// - if not already done - e.g. DeletedItemHistoryInterceptor already set as true,
153 		// so do not override this to false !
154 		// - if has an 'update_dt' column
155 		// - if NO status filter, or has 'status_cd' column
156 		// - if NO program filter, or is 'PROGRAMME' table
157 		if (!table.isRoot() && isRoot) {
158 			table.setRoot(true);
159 		}
160 
161 		// Special case for some tables without update_dt
162 		/*
163 		 * String tableName = table.getName();
164 		 * boolean isRootWithoutUpdateDate = !hasUpdateDate
165 		 * && ("A_ROOT_TABLE_WITHOUT_UPDATE_DT".equalsIgnoreCase(tableName));
166 		 * if (isRootWithoutUpdateDate) {
167 		 * table.setRoot(true);
168 		 * return;
169 		 * }
170 		 */
171 
172 		// File DB -> Local DB
173 		if (direction == SynchroDirection.IMPORT_FILE2LOCAL) {
174 
175 			// Natural Id: add as unique constraints with reject
176 			Collection<String> columnNames = naturalIds.get(table.getName());
177 			if (CollectionUtils.isNotEmpty(columnNames)) {
178 				// Choose the REPLACE_AND_REMAP strategy for forced tables, otherwise REJECT_AND_REMAP
179 				SynchroTableMetadata.DuplicateKeyStrategy duplicateKeyStrategy = getConfig().getTableNamesForced().contains(table.getName()) ?
180 						SynchroTableMetadata.DuplicateKeyStrategy.REPLACE_AND_REMAP :
181 						SynchroTableMetadata.DuplicateKeyStrategy.REJECT_AND_REMAP;
182 				table.addUniqueConstraint("NATURAL_ID_UNIQUE_C", ImmutableList.copyOf(columnNames), duplicateKeyStrategy);
183 			}
184 
185 			// Make sure PK are generate for local PK
186 			if (table.isSimpleKey()) {
187 
188 				String pkName = table.getPkNames().iterator().next();
189 				boolean isNumericPk = SynchroMetadataUtils.isNumericType(table.getColumnMetadata(pkName));
190 
191 				if (isNumericPk) {
192 					int pkColumnIndex = table.getSelectColumnIndex(pkName);
193 
194 					// Retrieve recursive columns, filled on method handleJoinLoad()
195 					Map<String, Integer> recursiveColumnIndexesByNames = recursiveColumnsByTable.get(table.getName());
196 
197 					ImportFromFileNumericalPkInterceptor pkInterceptor = new ImportFromFileNumericalPkInterceptor(
198 							table.getName(),
199 							pkName,
200 							pkColumnIndex,
201 							recursiveColumnIndexesByNames,
202 							getConfig()
203 							);
204 					table.addInterceptor(pkInterceptor);
205 				}
206 			}
207 		}
208 	}
209 
210 	/**
211 	 * <p>
212 	 * handleJoinLoad.
213 	 * </p>
214 	 * 
215 	 * @param e
216 	 *            a {@link fr.ifremer.common.synchro.meta.event.LoadJoinEvent} object.
217 	 */
218 	@Subscribe
219 	public void handleJoinLoad(LoadJoinEvent e) {
220 		SynchroJoinMetadata join = e.join;
221 
222 		SynchroTableMetadata fkTable = join.getFkTable();
223 		String fkTableName = join.getFkTable().getName().toUpperCase();
224 		String pkTableName = join.getPkTable().getName().toUpperCase();
225 		String fkColumnName = join.getFkColumn().getName().toUpperCase();
226 
227 		boolean hasStatusFilter = statusWhereClauseOrNull != null;
228 		boolean fkTableHasUpdateDateColumn = hasColumns(join.getFkTable(), DatabaseColumns.UPDATE_DT.name());
229 		boolean fkTableHasStatusCdColumn = hasColumns(join.getFkTable(), DatabaseColumns.STATUS_CD.name());
230 		boolean fkTableIsRoot = fkTableHasUpdateDateColumn
231 				&& (!hasStatusFilter || fkTableHasStatusCdColumn);
232 		boolean isNumericColumn = SynchroMetadataUtils.isNumericType(fkTable.getColumnMetadata(fkColumnName));
233 		SynchroDirection direction = getConfig().getDirection();
234 
235 		// Disable link to itself
236 		if (join.getFkTable() == join.getPkTable()) {
237 			if (log.isDebugEnabled()) {
238 				log.debug("Disable join: " + join.toString());
239 			}
240 			join.setIsValid(false);
241 
242 			// File DB -> Local DB
243 			if (direction == SynchroDirection.IMPORT_FILE2LOCAL
244 					&& isNumericColumn
245 					&& fkTable.isSimpleKey()) {
246 
247 				// Store this recursive FK column into a map, used in method handleTableLoad()
248 				Map<String, Integer> recursiveColumnIndexesByName = recursiveColumnsByTable.computeIfAbsent(pkTableName, k -> Maps.newHashMap());
249 				recursiveColumnIndexesByName.put(fkColumnName, fkTable.getSelectColumnIndex(fkColumnName));
250 			}
251 			return;
252 		}
253 
254 		// Disable all join to a root table
255 		if (join.isChild() && fkTableIsRoot) {
256 			if (log.isDebugEnabled()) {
257 				log.debug("Disable join: " + join.toString());
258 			}
259 			join.setIsValid(false);
260 			return;
261 		}
262 
263 		// Keep child join when status filter is enable (e.g. export to file)
264 		else if (join.isChild() && fkTableHasUpdateDateColumn && hasStatusFilter) {
265 			// Keep the join
266 			return;
267 		}
268 
269 		// Keep a child join if its includes
270 		if (join.isChild()) {
271 			// Is join explicitly include ?
272 			Collection<String> columnIncludes = childJoinIncludes.get(fkTableName);
273 			boolean explicitlyInclude = CollectionUtils.isNotEmpty(columnIncludes) && columnIncludes.contains(fkColumnName);
274 
275 			// Is join explicitly exclude ?
276 			Collection<String> columnExcludes = childJoinExcludes.get(fkTableName);
277 			boolean explicitlyExclude = CollectionUtils.isNotEmpty(columnExcludes) && columnExcludes.contains(fkColumnName);
278 
279 			if (!explicitlyInclude || explicitlyExclude) {
280 				if (log.isDebugEnabled()) {
281 					log.debug("Disable join: " + join.toString());
282 				}
283 				join.setIsValid(false);
284 			}
285 		}
286 
287 		// If the FK table is the current table
288 		if (fkTable == e.source) {
289 
290 			// File DB -> Local DB
291 			if (direction == SynchroDirection.IMPORT_FILE2LOCAL
292 					&& isNumericColumn
293 					&& join.getPkTable().isSimpleKey()) {
294 				int fkColumnIndex = fkTable.getSelectColumnIndex(fkColumnName);
295 
296 				ImportFromFileFkInterceptor fkInterceptor = new ImportFromFileFkInterceptor(
297 						pkTableName,
298 						fkColumnIndex,
299 						getConfig());
300 
301 				if (!fkTable.containsInterceptor(fkInterceptor)) {
302 					fkTable.addInterceptor(fkInterceptor);
303 				}
304 			}
305 		}
306 	}
307 
308 	/* -- Internal methods -- */
309 
310 	/**
311 	 * <p>
312 	 * createStatusWhereClauseOrNull.
313 	 * </p>
314 	 * 
315 	 * @param config
316 	 *            a {@link fr.ifremer.quadrige3.synchro.service.referential.ReferentialSynchroDatabaseConfiguration}
317 	 *            object.
318 	 * @return a {@link java.lang.String} object.
319 	 */
320 	protected String createStatusWhereClauseOrNull(ReferentialSynchroDatabaseConfiguration config) {
321 
322 		Set<String> statusCodeIncludes = config.getStatusCodeIncludes();
323 		if (CollectionUtils.isEmpty(statusCodeIncludes)) {
324 			return null;
325 		}
326 
327 		return String.format("t.%s IN ('%s')",
328 				DatabaseColumns.STATUS_CD,
329 				Joiner.on("','").join(statusCodeIncludes));
330 	}
331 
332 	/**
333 	 * <p>
334 	 * addRestriction.
335 	 * </p>
336 	 * 
337 	 * @param table
338 	 *            a {@link fr.ifremer.common.synchro.meta.SynchroTableMetadata} object.
339 	 * @param queryName
340 	 *            a {@link fr.ifremer.common.synchro.query.SynchroQueryName} object.
341 	 * @param sql
342 	 *            a {@link java.lang.String} object.
343 	 * @return a {@link java.lang.String} object.
344 	 */
345 	protected String addRestriction(SynchroTableMetadata table, SynchroQueryName queryName, String sql) {
346 		SynchroQueryBuilder queryBuilder = SynchroQueryBuilder.newBuilder(sql);
347 
348 		// where: add status filter
349 		if (hasColumns(table, DatabaseColumns.STATUS_CD.name())) {
350 			if (statusWhereClauseOrNull != null) {
351 				queryBuilder.addWhere(SynchroQueryOperator.AND, statusWhereClauseOrNull);
352 			} else
353 			// Add specific where clause for selectMaxUpdateDate query
354 			if (queryName == SynchroQueryName.selectMaxUpdateDate) {
355 				// Only for TEMP to LOCAL, ignore local referential update date
356 				if (getConfig().getDirection() == SynchroDirection.IMPORT_TEMP2LOCAL) {
357 					queryBuilder.addWhere(
358 							SynchroQueryOperator.AND,
359 							String.format("t.%s NOT IN ('%s', '%s')", DatabaseColumns.STATUS_CD.name(), StatusCode.LOCAL_ENABLE.getValue(),
360 									StatusCode.LOCAL_DISABLE.getValue()));
361 				}
362 				// Only for FILE to LOCAL, ignore max update
363 				else if (getConfig().getDirection() == SynchroDirection.IMPORT_FILE2LOCAL) {
364 					queryBuilder.addWhere(SynchroQueryOperator.AND, "1 = 2");
365 				}
366 			}
367 		}
368 
369 		// where: limit to pks (for import by Pk)
370 		String pkFilter = createPkFilter(table);
371 		if (StringUtils.isNotBlank(pkFilter)) {
372 			// Apply Pk filter, but do not apply date restriction
373 			queryBuilder.addWhere(SynchroQueryOperator.AND, pkFilter);
374 		}
375 
376 		return queryBuilder.build();
377 	}
378 
379 	/**
380 	 * <p>
381 	 * initChildJoinIncludes.
382 	 * </p>
383 	 * 
384 	 * @return a {@link com.google.common.collect.Multimap} object.
385 	 */
386 	protected static Multimap<String, String> initChildJoinIncludes() {
387 		Multimap<String, String> result = ArrayListMultimap.create();
388 
389 		// Only this child link will be kept (other are invalidate)
390 		result.put(ReferentialSynchroTables.CAMPAIGN_AREA.name(), "CAMPAIGN_ID");
391 		result.put(ReferentialSynchroTables.CAMPAIGN_LINE.name(), "CAMPAIGN_ID");
392 		result.put(ReferentialSynchroTables.CAMPAIGN_POINT.name(), "CAMPAIGN_ID");
393 		result.put(ReferentialSynchroTables.CAMPAIGN_PROG.name(), "CAMPAIGN_ID");
394 		result.put(ReferentialSynchroTables.DEPARTMENT_PRIVILEGE.name(), "DEP_ID");
395 		result.put(ReferentialSynchroTables.EVENT_AREA.name(), "EVENT_ID");
396 		result.put(ReferentialSynchroTables.EVENT_LINE.name(), "EVENT_ID");
397 		result.put(ReferentialSynchroTables.EVENT_POINT.name(), "EVENT_ID");
398 		// FRACTION_MATRIX is child of MATRIX for Q2 (see Mantis #0028388)
399 		result.put(ReferentialSynchroTables.FRACTION_MATRIX.name(), "MATRIX_ID");
400 		result.put(ReferentialSynchroTables.FRACTION_MATRIX.name(), "FRACTION_ID");
401 		result.put(ReferentialSynchroTables.MON_LOC_AREA.name(), "MON_LOC_ID");
402 		result.put(ReferentialSynchroTables.MON_LOC_LINE.name(), "MON_LOC_ID");
403 		result.put(ReferentialSynchroTables.MON_LOC_ORDER_ITEM.name(), "MON_LOC_ID");
404 		result.put(ReferentialSynchroTables.MON_LOC_ORDER_ITEM.name(), "ORDER_ITEM_ID");
405 		result.put(MetaProgramSynchroTables.MON_LOC_PMFM_MET.name(), "MON_LOC_MET_ID");
406 		result.put(ReferentialSynchroTables.MON_LOC_POINT.name(), "MON_LOC_ID");
407 		result.put(ReferentialSynchroTables.MON_LOC_PROG.name(), "PROG_CD");
408 		result.put(MetaProgramSynchroTables.MON_LOC_MET.name(), "PROG_CD");
409 		result.put(ReferentialSynchroTables.OCCAS_AREA.name(), "OCCAS_ID");
410 		result.put(ReferentialSynchroTables.OCCAS_LINE.name(), "OCCAS_ID");
411 		result.put(ReferentialSynchroTables.OCCAS_POINT.name(), "OCCAS_ID");
412 		result.put(ReferentialSynchroTables.OCCAS_QUSER.name(), "OCCAS_ID");
413 		result.put(ReferentialSynchroTables.PROG_DEP_PROG_PRIV.name(), "PROG_CD");
414 		result.put(MetaProgramSynchroTables.PMFM_MET.name(), "MET_CD");
415 		result.put(ReferentialSynchroTables.PMFM_QUAL_VALUE.name(), "PMFM_ID");
416 		result.put(ReferentialSynchroTables.PMFM_STRAT_ACQUIS_LEVEL.name(), "PMFM_STRAT_ID");
417 		result.put(ReferentialSynchroTables.PMFM_STRAT_UI_FUNCTION.name(), "PMFM_STRAT_ID");
418 		result.put(ReferentialSynchroTables.PMFM_STRAT_PMFM_QUAL_VALUE.name(), "PMFM_STRAT_ID");
419 		result.put(ReferentialSynchroTables.PROG_QUSER_PROG_PRIV.name(), "PROG_CD");
420 		result.put(ContextAndFilterSynchroTables.PMFM_CONTEXT_ORDER.name(), "CONTEXT_ID");
421 		result.put(ReferentialSynchroTables.QUSER_PRIVILEGE.name(), "QUSER_ID");
422 		result.put(ReferentialSynchroTables.VIRTUAL_COMPONENT.name(), "REF_TAXON_ID");
423 		result.put(ReferentialSynchroTables.TAXON_INFORMATION.name(), "TAXON_NAME_ID");
424 		result.put(ReferentialSynchroTables.TAXON_INFORMATION_HISTORY.name(), "TAXON_NAME_HIST_ID");
425 		result.put(ReferentialSynchroTables.TAXON_GROUP_INFORMATION.name(), "TAXON_GROUP_ID");
426 		result.put(ReferentialSynchroTables.TAXON_GROUP_HISTORICAL_RECORD.name(), "TAXON_GROUP_ID");
427 		result.put(ReferentialSynchroTables.TAXON_GROUP_POSITION.name(), "TAXON_GROUP_ID");
428 		result.put(ReferentialSynchroTables.PRIVILEGE_TRANSFER.name(), "PRIV_TRANSFER_FROM_DEP_ID");
429 		result.put(ReferentialSynchroTables.TAXON_POSITION.name(), "REF_TAXON_ID");
430 		result.put(ReferentialSynchroTables.TAXON_POSITION.name(), "MON_LOC_ID");
431 		result.put(ReferentialSynchroTables.AUTHOR_REF_DOC.name(), "REF_DOC_ID");
432 
433 		// Need to allow deletion importation on STRATEGY (mantis #28389)
434 		result.put(ReferentialSynchroTables.RESP_QUSER_STRAT.name(), "STRAT_ID");
435 		result.put(ReferentialSynchroTables.RESP_DEP_STRAT.name(), "STRAT_ID");
436 
437 		// Need to allow deletion importation on STRATEGY and children (Fis Mantis #0029640)
438 		result.put(ReferentialSynchroTables.STRATEGY.name(), "PROG_CD");
439 		result.put(ReferentialSynchroTables.APPLIED_STRATEGY.name(), "STRAT_ID");
440 		result.put(ReferentialSynchroTables.APPLIED_PERIOD.name(), "APPLIED_STRAT_ID");
441 		result.put(ReferentialSynchroTables.PMFM_APPLIED_STRATEGY.name(), "APPLIED_STRAT_ID");
442 		result.put(ReferentialSynchroTables.PMFM_STRATEGY.name(), "STRAT_ID");
443 
444 		// MORATORIUM
445 		result.put(ReferentialSynchroTables.MORATORIUM.name(), "PROG_CD");
446 		result.put(ReferentialSynchroTables.MOR_MON_LOC_PROG.name(), "MOR_ID");
447 		result.put(ReferentialSynchroTables.MOR_PERIOD.name(), "MOR_ID");
448 		result.put(ReferentialSynchroTables.PMFM_MOR.name(), "MOR_ID");
449 		result.put(ReferentialSynchroTables.MOR_CAMP.name(), "MOR_ID");
450 		result.put(ReferentialSynchroTables.MOR_OCCAS.name(), "MOR_ID");
451 
452 		return result;
453 	}
454 
455 	/**
456 	 * <p>
457 	 * initChildJoinExcludes.
458 	 * </p>
459 	 * 
460 	 * @return a {@link com.google.common.collect.Multimap} object.
461 	 */
462 	protected static Multimap<String, String> initChildJoinExcludes() {
463 		Multimap<String, String> result = ArrayListMultimap.create();
464 
465 		// this child link will be excluded
466 		result.put("TAXON_GROUP", "PARENT_TAXON_GROUP_FK");
467 		result.put("PMFM_APPLIED_STRATEGY", "PMFM_STRAT_ID");
468 
469 		return result;
470 	}
471 
472 	/**
473 	 * Define all natural Id, by table.
474 	 * <p/>
475 	 * See specification from file 'Maquette-Référentiels.v2.0.xlsx'
476 	 * 
477 	 * @return a {@link com.google.common.collect.Multimap} object.
478 	 */
479 	protected static Multimap<String, String> initNaturalIds() {
480 		Multimap<String, String> result = MultimapBuilder.hashKeys().arrayListValues().build();
481 		result.put(ReferentialSynchroTables.MONITORING_LOCATION.name(), DatabaseColumns.MON_LOC_NM.name());
482 		result.putAll(ReferentialSynchroTables.TAXON_NAME.name(), ImmutableList.of(
483 				DatabaseColumns.TAXON_NAME_COMPLETE_NM.name(),
484 				DatabaseColumns.CIT_ID.name()));
485 		result.put(ReferentialSynchroTables.TAXON_GROUP.name(), DatabaseColumns.TAXON_GROUP_NM.name());
486 		result.put(ReferentialSynchroTables.PARAMETER.name(), DatabaseColumns.PAR_CD.name());
487 		result.put(ReferentialSynchroTables.MATRIX.name(), DatabaseColumns.MATRIX_NM.name());
488 		result.put(ReferentialSynchroTables.FRACTION.name(), DatabaseColumns.FRACTION_NM.name());
489 		result.put(ReferentialSynchroTables.METHOD.name(), DatabaseColumns.METHOD_NM.name());
490 		result.putAll(ReferentialSynchroTables.QUALITATIVE_VALUE.name(), ImmutableList.of(
491 				DatabaseColumns.QUAL_VALUE_NM.name(),
492 				DatabaseColumns.PAR_CD.name()));
493 
494 		// PMFM
495 		result.putAll(ReferentialSynchroTables.PMFM.name(), ImmutableList.of(
496 				DatabaseColumns.PAR_CD.name(),
497 				DatabaseColumns.MATRIX_ID.name(),
498 				DatabaseColumns.FRACTION_ID.name(),
499 				DatabaseColumns.METHOD_ID.name(),
500 				DatabaseColumns.UNIT_ID.name()));
501 
502 		result.putAll(ReferentialSynchroTables.UNIT.name(), ImmutableList.of(
503 				DatabaseColumns.UNIT_NM.name(),
504 				DatabaseColumns.UNIT_SYMBOL.name()));
505 		result.put(ReferentialSynchroTables.ANALYSIS_INSTRUMENT.name(), DatabaseColumns.ANAL_INST_NM.name());
506 		result.put(ReferentialSynchroTables.SAMPLING_EQUIPMENT.name(), DatabaseColumns.SAMPLING_EQUIPMENT_NM.name());
507 		result.put(ReferentialSynchroTables.PROGRAMME.name(), DatabaseColumns.PROG_CD.name());
508 		result.putAll(ReferentialSynchroTables.STRATEGY.name(), ImmutableList.of(
509 				DatabaseColumns.PROG_CD.name(),
510 				DatabaseColumns.STRAT_NM.name()));
511 		result.put(ReferentialSynchroTables.QUSER.name(), DatabaseColumns.QUSER_INTRANET_LG.name());
512 		result.put(ReferentialSynchroTables.DEPARTMENT.name(), DatabaseColumns.DEP_CD.name());
513 
514 		return result;
515 	}
516 
517 }