1
|
/**
|
2
|
* Copyright (C) 2009 EDIT
|
3
|
* European Distributed Institute of Taxonomy
|
4
|
* http://www.e-taxonomy.eu
|
5
|
*
|
6
|
* The contents of this file are subject to the Mozilla Public License Version 1.1
|
7
|
* See LICENSE.TXT at the top of this package for the full license terms.
|
8
|
*/
|
9
|
package eu.etaxonomy.cdm.io.dwca.in;
|
10
|
|
11
|
import java.sql.ResultSet;
|
12
|
import java.sql.SQLException;
|
13
|
import java.util.HashMap;
|
14
|
import java.util.HashSet;
|
15
|
import java.util.Map;
|
16
|
import java.util.Map.Entry;
|
17
|
import java.util.Set;
|
18
|
|
19
|
import org.apache.log4j.Logger;
|
20
|
|
21
|
import eu.etaxonomy.cdm.database.CdmDataSource;
|
22
|
import eu.etaxonomy.cdm.database.CdmPersistentDataSource;
|
23
|
import eu.etaxonomy.cdm.database.DataSourceNotFoundException;
|
24
|
import eu.etaxonomy.cdm.database.ICdmDataSource;
|
25
|
import eu.etaxonomy.cdm.model.agent.Person;
|
26
|
import eu.etaxonomy.cdm.model.agent.Team;
|
27
|
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
|
28
|
import eu.etaxonomy.cdm.model.name.TaxonName;
|
29
|
import eu.etaxonomy.cdm.model.reference.Reference;
|
30
|
import eu.etaxonomy.cdm.model.taxon.Synonym;
|
31
|
import eu.etaxonomy.cdm.model.taxon.Taxon;
|
32
|
|
33
|
/**
|
34
|
* TODO inwork, currently it works with H2
|
35
|
* @author a.mueller
|
36
|
* @created 22.03.2012
|
37
|
*
|
38
|
*/
|
39
|
public class DatabaseMapping implements IImportMapping {
|
40
|
private static final Logger logger = Logger.getLogger(DatabaseMapping.class);
|
41
|
|
42
|
private static final String DATABASE_INTERNAL_IMPORT_MAPPING = "_internalImportMapping";
|
43
|
protected static final String TABLE_IMPORT_MAPPING = "importmapping";
|
44
|
|
45
|
private static final String COL_TASK_ID = "task_id";
|
46
|
|
47
|
private static final String COL_SOURCE_NS = "source_namespace";
|
48
|
|
49
|
private static final String COL_SOURCE_ID = "source_id";
|
50
|
|
51
|
private static final String COL_DEST_NS = "destination_namespace";
|
52
|
|
53
|
private static final String COL_DEST_ID = "destination_id";
|
54
|
|
55
|
private static final int SOURCE_KEY_LENGTH = 255;
|
56
|
|
57
|
|
58
|
private ICdmDataSource datasource;
|
59
|
private final String mappingId;
|
60
|
private final Map<String, Class> shortCuts = new HashMap<>();
|
61
|
private final Map<Class, String> reverseShortCuts = new HashMap<>();
|
62
|
|
63
|
|
64
|
@Override
|
65
|
public void putMapping(String namespace, Integer sourceKey, IdentifiableEntity destinationObject){
|
66
|
putMapping(namespace, String.valueOf(sourceKey), destinationObject);
|
67
|
}
|
68
|
|
69
|
public DatabaseMapping(String mappingId) {
|
70
|
this(mappingId, null);
|
71
|
}
|
72
|
|
73
|
/**
|
74
|
* @param database
|
75
|
*/
|
76
|
public DatabaseMapping(String mappingId, String file) {
|
77
|
super();
|
78
|
initDatasource(file);
|
79
|
this.mappingId = mappingId;
|
80
|
}
|
81
|
|
82
|
@Override
|
83
|
public void putMapping(String namespace, String sourceKey, IdentifiableEntity destinationObject){
|
84
|
CdmKey<IdentifiableEntity<?>> cdmKey = new CdmKey<>(destinationObject);
|
85
|
putMapping(namespace, sourceKey, cdmKey);
|
86
|
}
|
87
|
|
88
|
public void putMapping(String namespace, String sourceKey, CdmKey<IdentifiableEntity<?>> cdmKey) {
|
89
|
try {
|
90
|
String normalizedKey = normalizeKey(sourceKey);
|
91
|
|
92
|
deleteExistingMapping(namespace, sourceKey);
|
93
|
persistNotExistingMapping(namespace, normalizedKey, cdmKey);
|
94
|
} catch (SQLException e) {
|
95
|
throw new RuntimeException(e);
|
96
|
}
|
97
|
}
|
98
|
|
99
|
private int persistNotExistingMapping(String sourceNamespace, String normalizedKey, CdmKey<IdentifiableEntity<?>> cdmKey) throws SQLException {
|
100
|
|
101
|
//cdm namespace
|
102
|
String clazz = getCdmClassStr(cdmKey.clazz);
|
103
|
|
104
|
//insert
|
105
|
String insertMappingSql = " INSERT INTO %s (%s, %s, %s, %s, %s)" +
|
106
|
" VALUES ('%s','%s','%s','%s','%s')";
|
107
|
insertMappingSql = String.format(insertMappingSql,
|
108
|
TABLE_IMPORT_MAPPING, COL_TASK_ID, COL_SOURCE_NS, COL_SOURCE_ID, COL_DEST_NS, COL_DEST_ID,
|
109
|
this.mappingId, sourceNamespace, normalizedKey, clazz, cdmKey.id);
|
110
|
return this.datasource.executeUpdate(insertMappingSql);
|
111
|
}
|
112
|
|
113
|
|
114
|
/**
|
115
|
* @param cdmKey
|
116
|
* @return
|
117
|
*/
|
118
|
private String getCdmClassStr(Class cdmClass) {
|
119
|
String clazz = reverseShortCuts.get(cdmClass);
|
120
|
if (clazz == null){
|
121
|
clazz = cdmClass.getCanonicalName();
|
122
|
}
|
123
|
return clazz;
|
124
|
}
|
125
|
|
126
|
|
127
|
private int deleteExistingMapping(String sourceNamespace, String sourceId) throws SQLException {
|
128
|
String normalizedKey = normalizeKey(sourceId);
|
129
|
String deleteMappingSql = " DELETE FROM %s WHERE %s = '%s' AND %s = '%s' AND %s = '%s'";
|
130
|
deleteMappingSql = String.format(deleteMappingSql,TABLE_IMPORT_MAPPING, COL_TASK_ID, this.mappingId, COL_SOURCE_NS, sourceNamespace, COL_SOURCE_ID, normalizedKey);
|
131
|
return this.datasource.executeUpdate(deleteMappingSql);
|
132
|
}
|
133
|
|
134
|
|
135
|
private int deleteAll() throws SQLException {
|
136
|
String deleteMappingSql = " DELETE FROM %s WHERE %s = '%s' ";
|
137
|
deleteMappingSql = String.format(deleteMappingSql,TABLE_IMPORT_MAPPING, COL_TASK_ID, this.mappingId);
|
138
|
return this.datasource.executeUpdate(deleteMappingSql);
|
139
|
}
|
140
|
|
141
|
public int size() throws SQLException {
|
142
|
String sql = " SELECT count(*) as n FROM %s WHERE %s = '%s' ";
|
143
|
sql = String.format(sql,TABLE_IMPORT_MAPPING, COL_TASK_ID, this.mappingId);
|
144
|
ResultSet rs = this.datasource.executeQuery(sql);
|
145
|
rs.next();
|
146
|
return rs.getInt("n");
|
147
|
|
148
|
}
|
149
|
|
150
|
@Override
|
151
|
public Set<CdmKey> get(String sourceNamespace, String sourceId) {
|
152
|
Set<CdmKey> result = new HashSet<>();
|
153
|
String normalizedKey = normalizeKey(sourceId);
|
154
|
String selectMappingSql = " SELECT %s, %s FROM %s" +
|
155
|
" WHERE %s = '%s' AND %s = '%s' AND %s = '%s' ";
|
156
|
selectMappingSql = String.format(selectMappingSql,
|
157
|
COL_DEST_NS, COL_DEST_ID, TABLE_IMPORT_MAPPING,
|
158
|
COL_TASK_ID, this.mappingId, COL_SOURCE_NS, sourceNamespace,
|
159
|
COL_SOURCE_ID , normalizedKey);
|
160
|
try {
|
161
|
ResultSet rs = this.datasource.executeQuery(selectMappingSql);
|
162
|
while (rs.next()){
|
163
|
String clazzStr = rs.getString(COL_DEST_NS);
|
164
|
Object id = rs.getObject(COL_DEST_ID);
|
165
|
if (id == null){
|
166
|
throw new RuntimeException("Destination id for import mapping is 'null'");
|
167
|
}
|
168
|
|
169
|
Class<?> clazz = getCdmClass(clazzStr);
|
170
|
|
171
|
CdmKey<?> key = new CdmKey<>(clazz, Integer.valueOf(String.valueOf(id)));
|
172
|
result.add(key);
|
173
|
}
|
174
|
} catch (NumberFormatException e) {
|
175
|
throw new RuntimeException(e);
|
176
|
} catch (SQLException e) {
|
177
|
throw new RuntimeException(e);
|
178
|
} catch (ClassNotFoundException e) {
|
179
|
throw new RuntimeException(e);
|
180
|
}
|
181
|
|
182
|
return result;
|
183
|
}
|
184
|
|
185
|
@Override
|
186
|
public boolean exists(String sourceNamespace, String sourceId, Class<?> destinationClass){
|
187
|
String selectMappingSql = " SELECT count(*) as n FROM %s" +
|
188
|
" WHERE %s = '%s' AND %s = '%s' AND %s = '%s' AND %s = '%s' ";
|
189
|
|
190
|
|
191
|
String cdmClass = getCdmClassStr(destinationClass);
|
192
|
String normalizedKey = normalizeKey(sourceId);
|
193
|
selectMappingSql = String.format(selectMappingSql,
|
194
|
TABLE_IMPORT_MAPPING, COL_TASK_ID, this.mappingId,
|
195
|
COL_SOURCE_NS, sourceNamespace, COL_SOURCE_ID , normalizedKey, COL_DEST_NS, cdmClass);
|
196
|
try {
|
197
|
ResultSet rs = this.datasource.executeQuery(selectMappingSql);
|
198
|
rs.next();
|
199
|
int n = rs.getInt("n");
|
200
|
|
201
|
return n > 0;
|
202
|
} catch (SQLException e) {
|
203
|
throw new RuntimeException(e);
|
204
|
}
|
205
|
}
|
206
|
|
207
|
/**
|
208
|
* Normalizes the key coming from the DwCA File.
|
209
|
* This includes handling ' and keys with length > 255
|
210
|
* @param sourceKey
|
211
|
* @return
|
212
|
*/
|
213
|
private String normalizeKey(String key) {
|
214
|
if (key == null){
|
215
|
return null;
|
216
|
}
|
217
|
String result = key.replace("'", "''");
|
218
|
if (result.length() > SOURCE_KEY_LENGTH){
|
219
|
//TODO better use MD5 hash or similar
|
220
|
logger.info("Source key was trunkated: " + key);
|
221
|
result = result.substring(0, SOURCE_KEY_LENGTH);
|
222
|
}
|
223
|
return result;
|
224
|
}
|
225
|
|
226
|
@Override
|
227
|
public InMemoryMapping getPartialMapping( Map<String, Set<String>> namespacedSourceKeys) {
|
228
|
InMemoryMapping partialMapping = new InMemoryMapping();
|
229
|
for (Entry<String,Set<String>> entry : namespacedSourceKeys.entrySet()){
|
230
|
String namespace = entry.getKey();
|
231
|
for (String sourceKey : entry.getValue() ){
|
232
|
String normalizedKey = normalizeKey(sourceKey);
|
233
|
Set<CdmKey> destObjects = this.get(namespace, normalizedKey);
|
234
|
for (CdmKey cdmKey : destObjects){
|
235
|
partialMapping.putMapping(namespace, normalizedKey, cdmKey);
|
236
|
}
|
237
|
}
|
238
|
}
|
239
|
return partialMapping;
|
240
|
}
|
241
|
|
242
|
|
243
|
@Override
|
244
|
public void finish() {
|
245
|
try {
|
246
|
int count = size();
|
247
|
deleteAll();
|
248
|
logger.info("Finalize database mapping " + count + ": " + size());
|
249
|
} catch (SQLException e) {
|
250
|
throw new RuntimeException();
|
251
|
}
|
252
|
}
|
253
|
|
254
|
|
255
|
/**
|
256
|
* @param clazzStr
|
257
|
* @return
|
258
|
* @throws ClassNotFoundException
|
259
|
*/
|
260
|
private Class<?> getCdmClass(String clazzStr) throws ClassNotFoundException {
|
261
|
Class<?> clazz = shortCuts.get(clazzStr);
|
262
|
if (clazz == null){
|
263
|
clazz = Class.forName(clazzStr);
|
264
|
}
|
265
|
return clazz;
|
266
|
}
|
267
|
|
268
|
|
269
|
private void initDatasource(String file) {
|
270
|
getDatabase(file);
|
271
|
shortCuts.put("TaxonName", TaxonName.class);
|
272
|
shortCuts.put("Taxon", Taxon.class);
|
273
|
shortCuts.put("Synonym", Synonym.class);
|
274
|
shortCuts.put("Reference", Reference.class);
|
275
|
shortCuts.put("Team", Team.class);
|
276
|
shortCuts.put("Person", Person.class);
|
277
|
//reverse
|
278
|
for (String key :shortCuts.keySet()){
|
279
|
reverseShortCuts.put(shortCuts.get(key), key);
|
280
|
}
|
281
|
}
|
282
|
|
283
|
public ICdmDataSource getDatabase(){
|
284
|
return getDatabase(null);
|
285
|
}
|
286
|
|
287
|
public ICdmDataSource getDatabase(String path){
|
288
|
try {
|
289
|
try {
|
290
|
if (path == null){
|
291
|
datasource = CdmPersistentDataSource.NewInstance(DATABASE_INTERNAL_IMPORT_MAPPING);
|
292
|
}else{
|
293
|
makeDatasource(path);
|
294
|
}
|
295
|
} catch (DataSourceNotFoundException e) {
|
296
|
makeDatasource(path);
|
297
|
CdmPersistentDataSource.save(DATABASE_INTERNAL_IMPORT_MAPPING, datasource);
|
298
|
}
|
299
|
datasource.executeQuery("SELECT * FROM " + TABLE_IMPORT_MAPPING);
|
300
|
} catch (SQLException e) {
|
301
|
//create database structure
|
302
|
try {
|
303
|
String strCreateTable = "CREATE TABLE IF NOT EXISTS %s (";
|
304
|
strCreateTable += "%s nvarchar(36) NOT NULL,";
|
305
|
strCreateTable += "%s nvarchar(100) NOT NULL,";
|
306
|
strCreateTable += "%s nvarchar(" + SOURCE_KEY_LENGTH + ") NOT NULL,";
|
307
|
strCreateTable += "%s nvarchar(100) NOT NULL,";
|
308
|
strCreateTable += "destination_id nvarchar(50) NOT NULL,";
|
309
|
strCreateTable += "PRIMARY KEY (task_id, source_namespace, source_id)";
|
310
|
strCreateTable += ") ";
|
311
|
strCreateTable = String.format(strCreateTable, TABLE_IMPORT_MAPPING, COL_TASK_ID, COL_SOURCE_NS, COL_SOURCE_ID, COL_DEST_NS, COL_DEST_ID);
|
312
|
datasource.executeUpdate(strCreateTable);
|
313
|
logger.warn("Mapping database structure created");
|
314
|
} catch (SQLException e1) {
|
315
|
e1.printStackTrace();
|
316
|
throw new RuntimeException(e1);
|
317
|
}
|
318
|
}
|
319
|
return datasource;
|
320
|
}
|
321
|
|
322
|
/**
|
323
|
* @param path
|
324
|
*/
|
325
|
private void makeDatasource(String path) {
|
326
|
datasource = CdmDataSource.NewH2EmbeddedInstance("_tmpMapping", "a", "b", path);
|
327
|
}
|
328
|
|
329
|
|
330
|
}
|