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