View Javadoc
1   package fr.ifremer.quadrige2.synchro.intercept.system;
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.ArrayListMultimap;
28  import com.google.common.collect.ImmutableList;
29  import com.google.common.collect.Multimap;
30  import com.google.common.collect.MultimapBuilder;
31  import com.google.common.eventbus.Subscribe;
32  import fr.ifremer.common.synchro.intercept.SynchroInterceptorBase;
33  import fr.ifremer.common.synchro.meta.SynchroJoinMetadata;
34  import fr.ifremer.common.synchro.meta.SynchroMetadataUtils;
35  import fr.ifremer.common.synchro.meta.SynchroTableMetadata;
36  import fr.ifremer.common.synchro.meta.event.CreateQueryEvent;
37  import fr.ifremer.common.synchro.meta.event.LoadJoinEvent;
38  import fr.ifremer.common.synchro.meta.event.LoadTableEvent;
39  import fr.ifremer.common.synchro.query.SynchroQueryBuilder;
40  import fr.ifremer.common.synchro.query.SynchroQueryName;
41  import fr.ifremer.common.synchro.query.SynchroQueryOperator;
42  import fr.ifremer.common.synchro.service.SynchroDatabaseConfiguration;
43  import fr.ifremer.quadrige2.synchro.intercept.referential.AbstractReferentialInterceptor;
44  import fr.ifremer.quadrige2.synchro.intercept.referential.internal.ImportFromFileFkInterceptor;
45  import fr.ifremer.quadrige2.synchro.meta.DatabaseColumns;
46  import fr.ifremer.quadrige2.synchro.meta.system.RuleSynchroTables;
47  import fr.ifremer.quadrige2.synchro.service.SynchroDirection;
48  import fr.ifremer.quadrige2.synchro.service.referential.ReferentialSynchroDatabaseConfiguration;
49  import org.apache.commons.collections4.CollectionUtils;
50  import org.apache.commons.logging.Log;
51  import org.apache.commons.logging.LogFactory;
52  
53  import java.util.Collection;
54  import java.util.Set;
55  
56  /**
57   * Manage only table on rules
58   * 
59   * @author Benoit Lavenier <benoit.lavenier@e-is.pro>
60   * @since 1.0
61   */
62  public class RuleTableInterceptor extends AbstractReferentialInterceptor {
63  
64  	// Define all natural Id, by table
65  	/** Constant <code>naturalIds</code> */
66  	public static final Multimap<String, String> naturalIds;
67  	/** Constant <code>childJoinIncludes</code> */
68  	protected static final Multimap<String, String> childJoinIncludes;
69  	/** Constant <code>childJoinExcludes</code> */
70  	protected static final Multimap<String, String> childJoinExcludes;
71  	private static final Log log = LogFactory.getLog(RuleTableInterceptor.class);
72  
73  	static {
74  		childJoinIncludes = initChildJoinIncludes();
75  		childJoinExcludes = initChildJoinExcludes();
76  		naturalIds = initNaturalIds();
77  	}
78  
79  	private String statusWhereClause;
80  
81  	/**
82  	 * <p>
83  	 * Constructor for RuleTableInterceptor.
84  	 * </p>
85  	 */
86  	public RuleTableInterceptor() {
87  		super(RuleSynchroTables.tableNames());
88  	}
89  
90  	/**
91  	 * <p>
92  	 * initChildJoinIncludes.
93  	 * </p>
94  	 * 
95  	 * @return a {@link com.google.common.collect.Multimap} object.
96  	 */
97  	protected static Multimap<String, String> initChildJoinIncludes() {
98  		Multimap<String, String> result = ArrayListMultimap.create();
99  
100 		// Only this child link will be kept (other are invalidate)
101 		result.put(RuleSynchroTables.RULE_LIST_RESP_DEP.name(), DatabaseColumns.RULE_LIST_CD.name());
102 		result.put(RuleSynchroTables.RULE_LIST_PROG.name(), DatabaseColumns.RULE_LIST_CD.name());
103 		result.put(RuleSynchroTables.RULE_LIST_RESP_QUSER.name(), DatabaseColumns.RULE_LIST_CD.name());
104 		result.put(RuleSynchroTables.RULE_LIST_CONTROLED_DEP.name(), DatabaseColumns.RULE_LIST_CD.name());
105 		result.put(RuleSynchroTables.RULE.name(), DatabaseColumns.RULE_LIST_CD.name());
106 		result.put(RuleSynchroTables.RULE_PMFM.name(), DatabaseColumns.RULE_CD.name());
107 		result.put(RuleSynchroTables.RULE_PARAMETER.name(), DatabaseColumns.RULE_CD.name());
108 		result.put(RuleSynchroTables.RULE_PRECONDITION.name(), DatabaseColumns.RULE_CD.name());
109 
110 		return result;
111 	}
112 
113 	/**
114 	 * <p>
115 	 * initChildJoinExcludes.
116 	 * </p>
117 	 * 
118 	 * @return a {@link com.google.common.collect.Multimap} object.
119 	 */
120 	protected static Multimap<String, String> initChildJoinExcludes() {
121 		Multimap<String, String> result = ArrayListMultimap.create();
122 
123 		// this child link will be excluded
124 		// result.put("TAXON_GROUP", "PARENT_TAXON_GROUP_FK");
125 
126 		return result;
127 	}
128 
129 	/**
130 	 * <p>
131 	 * initNaturalIds.
132 	 * </p>
133 	 * 
134 	 * @return a {@link com.google.common.collect.Multimap} object.
135 	 */
136 	protected static Multimap<String, String> initNaturalIds() {
137 
138 		Multimap<String, String> result = MultimapBuilder.hashKeys().arrayListValues().build();
139 
140 		// RULE_LIST
141 		result.putAll(RuleSynchroTables.RULE_LIST.name(), ImmutableList.of(
142 				DatabaseColumns.RULE_LIST_CD.name()));
143 		// RULE
144 		result.putAll(RuleSynchroTables.RULE.name(), ImmutableList.of(
145 				DatabaseColumns.RULE_CD.name()));
146 
147 		return result;
148 	}
149 
150 	/** {@inheritDoc} */
151 	@Override
152 	protected void init(ReferentialSynchroDatabaseConfiguration config) {
153 		super.init(config);
154 		statusWhereClause = createStatusWhereClause(config);
155 		setEnableOnWrite(true);
156 	}
157 
158 	/** {@inheritDoc} */
159 	@Override
160 	public SynchroInterceptorBase clone() {
161 		RuleTableInterceptor result = (RuleTableInterceptor) super.clone();
162 		result.statusWhereClause = this.statusWhereClause;
163 		return result;
164 	}
165 
166 	/* -- Internal methods -- */
167 
168 	/**
169 	 * <p>
170 	 * handleQuery.
171 	 * </p>
172 	 * 
173 	 * @param e
174 	 *            a {@link fr.ifremer.common.synchro.meta.event.CreateQueryEvent} object.
175 	 */
176 	@Subscribe
177 	public void handleQuery(CreateQueryEvent e) {
178 
179 		switch (e.queryName) {
180 		case count:
181 		case countFromUpdateDate:
182 		case select:
183 		case selectFromUpdateDate:
184 		case selectMaxUpdateDate:
185 			// Add restriction
186 			e.sql = addRestriction(e.source, e.queryName, e.sql);
187 
188 		default:
189 			break;
190 		}
191 	}
192 
193 	/**
194 	 * <p>
195 	 * handleTableLoad.
196 	 * </p>
197 	 * 
198 	 * @param e
199 	 *            a {@link fr.ifremer.common.synchro.meta.event.LoadTableEvent} object.
200 	 */
201 	@Subscribe
202 	public void handleTableLoad(LoadTableEvent e) {
203 
204 		SynchroTableMetadata table = e.table;
205 		SynchroDirection direction = getConfig().getDirection();
206 
207 		boolean hasStatusFilter = statusWhereClause != null;
208 		boolean hasUpdateDateColumn = hasColumns(table, DatabaseColumns.UPDATE_DT.name());
209 		boolean hasStatusCdColumn = hasColumns(table, DatabaseColumns.STATUS_CD.name());
210 		boolean isRoot = hasUpdateDateColumn && (!hasStatusFilter || hasStatusCdColumn);
211 
212 		// Set isRoot :
213 		// - if not already done - e.g. DeletedItemHistoryInterceptor already set as true,
214 		// so do not override this to false !
215 		// - if has an 'update_dt' column
216 		// - if NO status filter, or has 'status_cd' column
217 		if (!table.isRoot() && isRoot) {
218 			table.setRoot(true);
219 		}
220 
221 		// Special case for some tables without status_cd (.e.g RULE_LIST)
222 		String tableName = table.getName();
223 		boolean isRootWithoutStatusCd = !hasStatusCdColumn && hasStatusFilter
224 				&& (tableName.equalsIgnoreCase(RuleSynchroTables.RULE_LIST.name()));
225 		if (!table.isRoot() && isRootWithoutStatusCd) {
226 			table.setRoot(true);
227 		}
228 
229 		// Define natural Id
230 		if (direction == SynchroDirection.IMPORT_FILE2LOCAL) {
231 			Collection<String> columnNames = naturalIds.get(table.getName());
232 			if (CollectionUtils.isNotEmpty(columnNames)) {
233 				table.addUniqueConstraint("NATURAL_ID_UNIQUE_C", ImmutableList.copyOf(columnNames),
234 						SynchroTableMetadata.DuplicateKeyStrategy.REPLACE_AND_REMAP);
235 			}
236 		}
237 	}
238 
239 	/**
240 	 * <p>
241 	 * handleJoinLoad.
242 	 * </p>
243 	 * 
244 	 * @param e
245 	 *            a {@link fr.ifremer.common.synchro.meta.event.LoadJoinEvent} object.
246 	 */
247 	@Subscribe
248 	public void handleJoinLoad(LoadJoinEvent e) {
249 		SynchroJoinMetadata join = e.join;
250 
251 		SynchroTableMetadata fkTable = join.getFkTable();
252 		String fkTableName = join.getFkTable().getName().toUpperCase();
253 		String pkTableName = join.getPkTable().getName().toUpperCase();
254 		String fkColumnName = join.getFkColumn().getName().toUpperCase();
255 
256 		boolean hasStatusFilter = statusWhereClause != null;
257 		boolean fkTableHasUpdateDateColumn = hasColumns(join.getFkTable(), DatabaseColumns.UPDATE_DT.name());
258 		boolean fkTableHasStatusCdColumn = hasColumns(join.getFkTable(), DatabaseColumns.STATUS_CD.name());
259 		boolean fkTableIsRoot = fkTableHasUpdateDateColumn && (!hasStatusFilter || fkTableHasStatusCdColumn);
260 		boolean isNumericColumn = SynchroMetadataUtils.isNumericType(fkTable.getColumnMetadata(fkColumnName));
261 		SynchroDirection direction = getConfig().getDirection();
262 
263 		// Disable link to itself
264 		if (join.getFkTable() == join.getPkTable()) {
265 			if (log.isDebugEnabled()) {
266 				log.debug("Disable join: " + join.toString());
267 			}
268 			join.setIsValid(false);
269 			return;
270 		}
271 
272 		// Disable all join to a root table
273 		if (join.isChild() && fkTableIsRoot) {
274 			if (log.isDebugEnabled()) {
275 				log.debug("Disable join: " + join.toString());
276 			}
277 			join.setIsValid(false);
278 			return;
279 		} else if (join.isChild() && fkTableHasUpdateDateColumn && hasStatusFilter) {
280 			// Keep the join
281 			return;
282 		}
283 
284 		// Keep a child join if its includes
285 		if (join.isChild()) {
286 			// Is join explicitly include ?
287 			Collection<String> columnIncludes = childJoinIncludes.get(fkTableName);
288 			boolean explicitlyInclude = CollectionUtils.isNotEmpty(columnIncludes) && columnIncludes.contains(fkColumnName);
289 
290 			// Is join explicitly exclude ?
291 			Collection<String> columnExcludes = childJoinExcludes.get(fkTableName);
292 			boolean explicitlyExclude = CollectionUtils.isNotEmpty(columnExcludes) && columnExcludes.contains(fkColumnName);
293 
294 			if (!explicitlyInclude || explicitlyExclude) {
295 				if (log.isDebugEnabled()) {
296 					log.debug("Disable join: " + join.toString());
297 				}
298 				join.setIsValid(false);
299 			}
300 		}
301 
302 		// Mantis #0029644 : add ImportFromFileFkInterceptor for Rules Tables
303 		// If the FK table is the current table
304 		if (fkTable == e.source) {
305 
306 			// File DB -> Local DB
307 			if (direction == SynchroDirection.IMPORT_FILE2LOCAL
308 					&& isNumericColumn
309 					&& join.getPkTable().isSimpleKey()) {
310 				int fkColumnIndex = fkTable.getSelectColumnIndex(fkColumnName);
311 
312 				ImportFromFileFkInterceptor fkInterceptor = new ImportFromFileFkInterceptor(
313 						pkTableName,
314 						fkColumnIndex,
315 						getConfig());
316 
317 				if (!fkTable.containsInterceptor(fkInterceptor)) {
318 					fkTable.addInterceptor(fkInterceptor);
319 				}
320 			}
321 		}
322 	}
323 
324 	/**
325 	 * <p>
326 	 * createStatusWhereClause.
327 	 * </p>
328 	 * 
329 	 * @param config
330 	 *            a {@link fr.ifremer.common.synchro.service.SynchroDatabaseConfiguration} object.
331 	 * @return a {@link java.lang.String} object.
332 	 */
333 	protected String createStatusWhereClause(SynchroDatabaseConfiguration config) {
334 
335 		Set<String> statusToInclude = getConfig().getStatusCodeIncludes();
336 		if (CollectionUtils.isEmpty(statusToInclude)) {
337 			return null;
338 		}
339 
340 		return String.format("t.%s IN ('%s')",
341 				DatabaseColumns.STATUS_CD,
342 				Joiner.on("','").join(statusToInclude));
343 	}
344 
345 	/**
346 	 * <p>
347 	 * addRestriction.
348 	 * </p>
349 	 * 
350 	 * @param table
351 	 *            a {@link fr.ifremer.common.synchro.meta.SynchroTableMetadata} object.
352 	 * @param queryName
353 	 *            a {@link fr.ifremer.common.synchro.query.SynchroQueryName} object.
354 	 * @param sql
355 	 *            a {@link java.lang.String} object.
356 	 * @return a {@link java.lang.String} object.
357 	 */
358 	protected String addRestriction(SynchroTableMetadata table, SynchroQueryName queryName, String sql) {
359 		SynchroQueryBuilder queryBuilder = SynchroQueryBuilder.newBuilder(sql);
360 
361 		// where: add status filter
362 		if (statusWhereClause != null
363 				&& hasColumns(table, DatabaseColumns.STATUS_CD.name())) {
364 			queryBuilder.addWhere(SynchroQueryOperator.AND, statusWhereClause);
365 		}
366 
367 		return queryBuilder.build();
368 	}
369 }