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.eventbus.Subscribe;
27  import fr.ifremer.common.synchro.dao.SynchroTableDao;
28  import fr.ifremer.common.synchro.intercept.SynchroInterceptorBase;
29  import fr.ifremer.common.synchro.intercept.SynchroOperationRepository;
30  import fr.ifremer.common.synchro.meta.SynchroTableMetadata;
31  import fr.ifremer.common.synchro.meta.event.CreateQueryEvent;
32  import fr.ifremer.common.synchro.meta.event.LoadTableEvent;
33  import fr.ifremer.common.synchro.query.SynchroQueryBuilder;
34  import fr.ifremer.common.synchro.query.SynchroQueryName;
35  import fr.ifremer.common.synchro.query.SynchroQueryOperator;
36  import fr.ifremer.quadrige3.core.dao.referential.StatusCode;
37  import fr.ifremer.quadrige3.core.dao.technical.Daos;
38  import fr.ifremer.quadrige3.synchro.meta.DatabaseColumns;
39  import fr.ifremer.quadrige3.synchro.meta.data.DataSynchroTables;
40  import fr.ifremer.quadrige3.synchro.meta.referential.ReferentialSynchroTables;
41  import fr.ifremer.quadrige3.synchro.service.SynchroDirection;
42  import fr.ifremer.quadrige3.synchro.service.referential.ReferentialSynchroContext;
43  import fr.ifremer.quadrige3.synchro.service.referential.ReferentialSynchroDatabaseConfiguration;
44  import org.apache.commons.collections4.CollectionUtils;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  
48  import java.io.IOException;
49  import java.sql.PreparedStatement;
50  import java.sql.ResultSet;
51  import java.sql.SQLException;
52  import java.sql.Types;
53  import java.util.List;
54  import java.util.Set;
55  
56  /**
57   * Manage table TAXON_REFERENCE :
58   * <ul>
59   * <li>Define this table as a root table, even if filtering on status</li>
60   * <li>If filtering on a local status, then add a where clause 'REF_TAXON_ID &lt; 0'</li>
61   * </ul>
62   *
63   * @author Benoit Lavenier <benoit.lavenier@e-is.pro>
64   * @since 1.0
65   */
66  public class ReferenceTaxonInterceptor extends AbstractReferentialInterceptor {
67  
68  	private static final Log log = LogFactory.getLog(ReferenceTaxonInterceptor.class);
69  
70  	static String UPDATED_ITEM_HISTORY_TABLE = "UPDATED_ITEM_HISTORY";
71  	private String localOnlyWhereClause;
72  	private PreparedStatement updatedItemHistoryTableExistsStatement = null;
73  	private PreparedStatement selectNewRefTaxonIdStatement = null;
74  	private PreparedStatement updateTaxonMeasurementByRefTaxonIdStatement = null;
75  	private Boolean updatedItemHistoryTableExists = null;
76  
77  	/**
78  	 * <p>
79  	 * Constructor for ReferenceTaxonInterceptor.
80  	 * </p>
81  	 */
82  	public ReferenceTaxonInterceptor() {
83  		super(ReferentialSynchroTables.REFERENCE_TAXON.name());
84  		setEnableOnWrite(true);
85  	}
86  
87  	/** {@inheritDoc} */
88  	@Override
89  	protected void init(ReferentialSynchroDatabaseConfiguration config) {
90  		localOnlyWhereClause = createLocalOnlyWhereClause(config);
91  	}
92  
93  	/** {@inheritDoc} */
94  	@Override
95  	public SynchroInterceptorBase clone() {
96  		ReferenceTaxonInterceptor result = (ReferenceTaxonInterceptor) super.clone();
97  		result.localOnlyWhereClause = this.localOnlyWhereClause;
98  		return result;
99  	}
100 
101 	@Override
102 	protected void doOnDelete(List<Object> pk, SynchroTableDao sourceDao, SynchroTableDao targetDao, SynchroOperationRepository buffer) throws SQLException {
103 
104 		ReferentialSynchroContext context = (ReferentialSynchroContext) buffer.getSynchroContext();
105 
106 		if (context.getDirection() == SynchroDirection.IMPORT_TEMP2LOCAL || context.getDirection() == SynchroDirection.IMPORT_FILE2LOCAL) {
107 			// Get reference taxon id to delete
108 			Long id = Daos.convertToLong(pk.get(0));
109 
110 			// Reset reference taxon id in taxon measurements on target (Mantis #52993)
111 			updateTaxonMeasurementByRefTaxonId(sourceDao, targetDao, id);
112 		}
113 
114 	}
115 
116 	private void updateTaxonMeasurementByRefTaxonId(SynchroTableDao sourceDao, SynchroTableDao targetDao, Long id) throws SQLException {
117 		if (updateTaxonMeasurementByRefTaxonIdStatement == null || updateTaxonMeasurementByRefTaxonIdStatement.isClosed()) {
118 			updateTaxonMeasurementByRefTaxonIdStatement = targetDao.getPreparedStatement(getUpdateTaxonMeasurementByRefTaxonIdQuery());
119 		}
120 
121 		// Try to get the new reference taxon id
122 		Long newRefTaxonId = findNewRefTaxonId(sourceDao, id);
123 		if (newRefTaxonId != null) {
124 			updateTaxonMeasurementByRefTaxonIdStatement.setLong(1, newRefTaxonId); // set
125 		} else {
126 			updateTaxonMeasurementByRefTaxonIdStatement.setNull(1, Types.NUMERIC);
127 		}
128 		updateTaxonMeasurementByRefTaxonIdStatement.setLong(2, id); // where
129 		updateTaxonMeasurementByRefTaxonIdStatement.execute();
130 	}
131 
132 	private Long findNewRefTaxonId(SynchroTableDao dao, Long id) {
133 		// Check UPDATED_ITEM_HISTORY table exists
134 		if (historyTableExists(dao)) {
135 
136 			ResultSet resultSet = null;
137 			try {
138 				try {
139 					if (selectNewRefTaxonIdStatement == null || selectNewRefTaxonIdStatement.isClosed()) {
140 						selectNewRefTaxonIdStatement = dao.getPreparedStatement(
141 							String.format("select NEW_VALUE from %s where OBJECT_TYPE_CD='%s' and COLUMN_NAME='%s' and OLD_VALUE=?",
142 								UPDATED_ITEM_HISTORY_TABLE,
143 								ReferentialSynchroTables.TAXON_NAME.name(),
144 								DatabaseColumns.REF_TAXON_ID)
145 						);
146 					}
147 					selectNewRefTaxonIdStatement.setString(1, id.toString());
148 					resultSet = selectNewRefTaxonIdStatement.executeQuery();
149 					resultSet.next();
150 					String result = resultSet.getString(1);
151 					if (result != null) {
152 						return Long.parseLong(result);
153 					}
154 				} catch (SQLException | NumberFormatException e) {
155 					log.warn(String.format("Can't get new %s (from deleted %s) from %s : %s", DatabaseColumns.REF_TAXON_ID, id, UPDATED_ITEM_HISTORY_TABLE, e.getMessage()));
156 				}
157 			} finally {
158 				Daos.closeSilently(resultSet);
159 			}
160 
161 		}
162 		return null;
163 	}
164 
165 	private boolean historyTableExists(SynchroTableDao dao) {
166 		if (updatedItemHistoryTableExists != null) {
167 			return updatedItemHistoryTableExists;
168 		}
169 
170 		ResultSet resultSet = null;
171 		try {
172 			try {
173 				if (updatedItemHistoryTableExistsStatement == null || updatedItemHistoryTableExistsStatement.isClosed()) {
174 					updatedItemHistoryTableExistsStatement = dao.getPreparedStatement(getUpdatedItemHistoryTableExistsQuery());
175 				}
176 				resultSet = updatedItemHistoryTableExistsStatement.executeQuery();
177 				resultSet.next();
178 				updatedItemHistoryTableExists = resultSet.getLong(1) != 0;
179 			} catch (SQLException e) {
180 				log.warn(String.format("Can't check if table %s exists: %s", UPDATED_ITEM_HISTORY_TABLE, e.getMessage()));
181 				updatedItemHistoryTableExists = false;
182 			}
183 		} finally {
184 			Daos.closeSilently(resultSet);
185 		}
186 
187 		return updatedItemHistoryTableExists;
188 	}
189 
190 	private String getUpdatedItemHistoryTableExistsQuery() {
191 		return String.format("select count(*) from INFORMATION_SCHEMA.TABLES where TABLE_NAME='%s'", UPDATED_ITEM_HISTORY_TABLE);
192 	}
193 
194 	@Override
195 	protected void doClose() throws IOException {
196 		super.doClose();
197 
198 		Daos.closeSilently(updatedItemHistoryTableExistsStatement);
199 		updatedItemHistoryTableExistsStatement = null;
200 		Daos.closeSilently(selectNewRefTaxonIdStatement);
201 		selectNewRefTaxonIdStatement = null;
202 		Daos.closeSilently(updateTaxonMeasurementByRefTaxonIdStatement);
203 		updateTaxonMeasurementByRefTaxonIdStatement = null;
204 	}
205 
206 	/**
207 	 * <p>
208 	 * handleQuery.
209 	 * </p>
210 	 *
211 	 * @param e
212 	 *            a {@link fr.ifremer.common.synchro.meta.event.CreateQueryEvent} object.
213 	 */
214 	@Subscribe
215 	public void handleQuery(CreateQueryEvent e) {
216 
217 		switch (e.queryName) {
218 		case count:
219 		case countFromUpdateDate:
220 		case select:
221 		case selectFromUpdateDate:
222 		case selectMaxUpdateDate:
223 			// Add restriction
224 			e.sql = addRestriction(e.source, e.queryName, e.sql);
225 
226 		default:
227 			break;
228 		}
229 	}
230 
231 	/**
232 	 * <p>
233 	 * handleTableLoad.
234 	 * </p>
235 	 *
236 	 * @param e
237 	 *            a {@link fr.ifremer.common.synchro.meta.event.LoadTableEvent} object.
238 	 */
239 	@Subscribe
240 	public void handleTableLoad(LoadTableEvent e) {
241 		SynchroTableMetadata table = e.table;
242 
243 		// Set has root (if not already done by ReferentialTableInterceptor)
244 		if (!table.isRoot()
245 				&& localOnlyWhereClause != null) {
246 			table.setRoot(true);
247 		}
248 	}
249 
250 	/* -- Internal methods -- */
251 
252 	/**
253 	 * <p>
254 	 * createLocalOnlyWhereClause.
255 	 * </p>
256 	 *
257 	 * @param config
258 	 *            a {@link fr.ifremer.quadrige3.synchro.service.referential.ReferentialSynchroDatabaseConfiguration}
259 	 *            object.
260 	 * @return a {@link java.lang.String} object.
261 	 */
262 	protected String createLocalOnlyWhereClause(ReferentialSynchroDatabaseConfiguration config) {
263 
264 		Set<String> statusCodeIncludes = config.getStatusCodeIncludes();
265 		if (CollectionUtils.isEmpty(statusCodeIncludes)
266 				|| (!statusCodeIncludes.contains(StatusCode.LOCAL_DISABLE.getValue())
267 				&& !statusCodeIncludes.contains(StatusCode.LOCAL_ENABLE.getValue()))) {
268 			return null;
269 		}
270 
271 		// Filter on ID, because this table does NOT have a STATUS_CD
272 		return String.format("t.%s < 0",
273 				DatabaseColumns.REF_TAXON_ID);
274 	}
275 
276 	/**
277 	 * <p>
278 	 * addRestriction.
279 	 * </p>
280 	 *
281 	 * @param table
282 	 *            a {@link fr.ifremer.common.synchro.meta.SynchroTableMetadata} object.
283 	 * @param queryName
284 	 *            a {@link fr.ifremer.common.synchro.query.SynchroQueryName} object.
285 	 * @param sql
286 	 *            a {@link java.lang.String} object.
287 	 * @return a {@link java.lang.String} object.
288 	 */
289 	protected String addRestriction(SynchroTableMetadata table, SynchroQueryName queryName, String sql) {
290 
291 		// where: add status filter
292 		if (localOnlyWhereClause == null) {
293 			return sql;
294 		}
295 
296 		SynchroQueryBuilder queryBuilder = SynchroQueryBuilder.newBuilder(sql);
297 		queryBuilder.addWhere(SynchroQueryOperator.AND, localOnlyWhereClause);
298 
299 		return queryBuilder.build();
300 	}
301 
302 	/*
303 	 * Create query to reset REF_TAXON_ID on TAXON_MEASUREMENT
304 	 */
305 	private String getUpdateTaxonMeasurementByRefTaxonIdQuery() {
306 		return String.format(
307 			"UPDATE %s SET %s=? WHERE %s=?",
308 			DataSynchroTables.TAXON_MEASUREMENT.name(),
309 			DatabaseColumns.REF_TAXON_ID.name(),
310 			DatabaseColumns.REF_TAXON_ID.name()
311 		);
312 	}
313 
314 }