View Javadoc

1   // $Id$
2   /**
3   * Copyright (C) 2009 EDIT
4   * European Distributed Institute of Taxonomy 
5   * http://www.e-taxonomy.eu
6   * 
7   * The contents of this file are subject to the Mozilla Public License Version 1.1
8   * See LICENSE.TXT at the top of this package for the full license terms.
9   */
10  package eu.etaxonomy.cdm.io.pesi.out;
11  
12  import java.sql.Connection;
13  import java.sql.PreparedStatement;
14  import java.sql.SQLException;
15  import java.util.ArrayList;
16  import java.util.HashMap;
17  import java.util.List;
18  import java.util.Set;
19  
20  import org.apache.log4j.Logger;
21  import org.springframework.stereotype.Component;
22  import org.springframework.transaction.TransactionStatus;
23  
24  import eu.etaxonomy.cdm.io.berlinModel.out.mapper.MethodMapper;
25  import eu.etaxonomy.cdm.io.common.DbExportStateBase;
26  import eu.etaxonomy.cdm.io.common.Source;
27  import eu.etaxonomy.cdm.model.common.CdmBase;
28  import eu.etaxonomy.cdm.model.common.RelationshipBase;
29  import eu.etaxonomy.cdm.model.name.NameRelationship;
30  import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
31  import eu.etaxonomy.cdm.model.name.Rank;
32  import eu.etaxonomy.cdm.model.name.TaxonNameBase;
33  import eu.etaxonomy.cdm.model.taxon.Synonym;
34  import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
35  import eu.etaxonomy.cdm.model.taxon.Taxon;
36  import eu.etaxonomy.cdm.model.taxon.TaxonBase;
37  import eu.etaxonomy.cdm.model.taxon.TaxonNode;
38  import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
39  import eu.etaxonomy.cdm.model.taxon.TaxonomicTree;
40  
41  /**
42   * The export class for relations between {@link eu.etaxonomy.cdm.model.taxon.TaxonBase TaxonBases}.<p>
43   * Inserts into DataWarehouse database table <code>RelTaxon</code>.
44   * @author e.-m.lee
45   * @date 23.02.2010
46   *
47   */
48  @Component
49  @SuppressWarnings("unchecked")
50  public class PesiRelTaxonExport extends PesiExportBase {
51  	private static final Logger logger = Logger.getLogger(PesiRelTaxonExport.class);
52  	private static final Class<? extends CdmBase> standardMethodParameter = RelationshipBase.class;
53  
54  	private static int modCount = 1000;
55  	private static final String dbTableName = "RelTaxon";
56  	private static final String pluralString = "Relationships";
57  	private static PreparedStatement synonymsStmt;
58  	private HashMap<Rank, Rank> rankMap = new HashMap<Rank, Rank>();
59  	private List<Rank> rankList = new ArrayList<Rank>();
60  	private PesiExportMapping mapping;
61  	private int count = 0;
62  	private boolean success = true;
63  	private static NomenclaturalCode nomenclaturalCode;
64  	
65  	public PesiRelTaxonExport() {
66  		super();
67  	}
68  
69  	/* (non-Javadoc)
70  	 * @see eu.etaxonomy.cdm.io.common.DbExportBase#getStandardMethodParameter()
71  	 */
72  	@Override
73  	public Class<? extends CdmBase> getStandardMethodParameter() {
74  		return standardMethodParameter;
75  	}
76  
77  	/* (non-Javadoc)
78  	 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doCheck(eu.etaxonomy.cdm.io.common.IoStateBase)
79  	 */
80  	@Override
81  	protected boolean doCheck(PesiExportState state) {
82  		boolean result = true;
83  		return result;
84  	}
85  
86  	/* (non-Javadoc)
87  	 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doInvoke(eu.etaxonomy.cdm.io.common.IoStateBase)
88  	 */
89  	@Override
90  	protected boolean doInvoke(PesiExportState state) {
91  		try {
92  			logger.error("*** Started Making " + pluralString + " ...");
93  	
94  			Connection connection = state.getConfig().getDestination().getConnection();
95  			String synonymsSql = "UPDATE Taxon SET KingdomFk = ?, RankFk = ?, RankCache = ? WHERE TaxonId = ?"; 
96  			synonymsStmt = connection.prepareStatement(synonymsSql);
97  			
98  			// Get the limit for objects to save within a single transaction.
99  			int pageSize = 1000;
100 
101 			// Get the limit for objects to save within a single transaction.
102 			int limit = state.getConfig().getLimitSave();
103 
104 			// Stores whether this invoke was successful or not.
105 			boolean success = true;
106 
107 			// PESI: Clear the database table RelTaxon.
108 			doDelete(state);
109 	
110 			// Get specific mappings: (CDM) Relationship -> (PESI) RelTaxon
111 			mapping = getMapping();
112 
113 			// Initialize the db mapper
114 			mapping.initialize(state);
115 
116 			TransactionStatus txStatus = null;
117 			List<TaxonomicTree> taxonomicTreeList = null;
118 			
119 			// Specify starting ranks for tree traversing
120 			rankList.add(Rank.KINGDOM());
121 			rankList.add(Rank.GENUS());
122 
123 			// Specify where to stop traversing (value) when starting at a specific Rank (key)
124 			rankMap.put(Rank.GENUS(), null); // Since NULL does not match an existing Rank, traverse all the way down to the leaves
125 			rankMap.put(Rank.KINGDOM(), Rank.GENUS()); // excludes rank genus
126 			
127 			// Retrieve list of Taxonomic Trees
128 			txStatus = startTransaction(true);
129 			logger.error("Started transaction. Fetching all Taxonomic Trees...");
130 			taxonomicTreeList = getTaxonTreeService().listTaxonomicTrees(null, 0, null, null);
131 			commitTransaction(txStatus);
132 			logger.error("Committed transaction.");
133 
134 			logger.error("Fetched " + taxonomicTreeList.size() + " Taxonomic Tree.");
135 
136 			for (TaxonomicTree taxonomicTree : taxonomicTreeList) {
137 				for (Rank rank : rankList) {
138 					
139 					txStatus = startTransaction(true);
140 					logger.error("Started transaction to fetch all rootNodes specific to Rank " + rank.getLabel() + " ...");
141 
142 					List<TaxonNode> rankSpecificRootNodes = getTaxonTreeService().loadRankSpecificRootNodes(taxonomicTree, rank, null);
143 					logger.error("Fetched " + rankSpecificRootNodes.size() + " RootNodes for Rank " + rank.getLabel());
144 
145 					commitTransaction(txStatus);
146 					logger.error("Committed transaction.");
147 
148 					for (TaxonNode rootNode : rankSpecificRootNodes) {
149 						txStatus = startTransaction(false);
150 						Rank endRank = rankMap.get(rank);
151 						if (endRank != null) {
152 							logger.error("Started transaction to traverse childNodes of rootNode (" + rootNode.getUuid() + ") till Rank " + endRank.getLabel() + " ...");
153 						} else {
154 							logger.error("Started transaction to traverse childNodes of rootNode (" + rootNode.getUuid() + ") till leaves are reached ...");
155 						}
156 
157 						TaxonNode newNode = getTaxonNodeService().load(rootNode.getUuid());
158 
159 						TaxonNode parentNode = newNode.getParent();
160 
161 						traverseTree(newNode, parentNode, rankMap.get(rank), state);
162 
163 						commitTransaction(txStatus);
164 						logger.error("Committed transaction.");
165 
166 					}
167 				}
168 			}
169 
170 			logger.error("*** Finished Making " + pluralString + " ..." + getSuccessString(success));
171 
172 
173 			return success;
174 		} catch (SQLException e) {
175 			e.printStackTrace();
176 			logger.error(e.getMessage());
177 			return false;
178 		}
179 	}
180 
181 	/**
182 	 * Traverses the TaxonTree recursively and stores determined values for every Taxon.
183 	 * @param childNode
184 	 * @param parentNode
185 	 * @param treeIndex
186 	 * @param fetchLevel
187 	 * @param state
188 	 */
189 	private void traverseTree(TaxonNode childNode, TaxonNode parentNode, Rank fetchLevel, PesiExportState state) {
190 		// Traverse all branches from this childNode until specified fetchLevel is reached.
191 		if (childNode.getTaxon() != null) {
192 			TaxonNameBase taxonName = childNode.getTaxon().getName();
193 			if (taxonName != null) {
194 				Rank childTaxonNameRank = taxonName.getRank();
195 				if (childTaxonNameRank != null) {
196 					if (! childTaxonNameRank.equals(fetchLevel)) {
197 
198 						saveData(childNode, parentNode, state);
199 
200 						for (TaxonNode newNode : childNode.getChildNodes()) {
201 							traverseTree(newNode, childNode, fetchLevel, state);
202 						}
203 						
204 					} else {
205 //						logger.error("Target Rank " + fetchLevel.getLabel() + " reached");
206 						return;
207 					}
208 				} else {
209 					logger.error("Rank is NULL. FetchLevel can not be checked: " + taxonName.getUuid() + " (" + taxonName.getTitleCache() + ")");
210 				}
211 			} else {
212 				logger.error("TaxonName is NULL for this node: " + childNode.getUuid());
213 			}
214 
215 		} else {
216 			logger.error("Taxon is NULL for TaxonNode: " + childNode.getUuid());
217 		}
218 	}
219 
220 	/**
221 	 * Stores values in database for every recursive round.
222 	 * @param childNode
223 	 * @param parentNode
224 	 * @param treeIndex
225 	 * @param state
226 	 * @param currentTaxonFk
227 	 */
228 	private void saveData(TaxonNode childNode, TaxonNode parentNode, PesiExportState state) {
229 		Taxon childNodeTaxon = childNode.getTaxon();
230 		if (childNodeTaxon != null) {
231 			TaxonNameBase childNodeTaxonName = childNodeTaxon.getName();
232 			nomenclaturalCode = PesiTransformer.getNomenclaturalCode(childNodeTaxonName);
233 
234 			if (childNodeTaxonName != null) {
235 
236 				// TaxonRelationships
237 				Set<Taxon> taxa = childNodeTaxonName.getTaxa(); // accepted taxa
238 				if (taxa.size() == 1) {
239 					Taxon taxon = CdmBase.deproxy(taxa.iterator().next(), Taxon.class);
240 					Set<TaxonRelationship> taxonRelations = taxon.getRelationsToThisTaxon();
241 					for (TaxonRelationship taxonRelationship : taxonRelations) {
242 						try {
243 							if (neededValuesNotNull(taxonRelationship, state)) {
244 								doCount(count++, modCount, pluralString);
245 								success &= mapping.invoke(taxonRelationship);
246 							}
247 						} catch (SQLException e) {
248 							logger.error("TaxonRelationship could not be created for this TaxonRelation (" + taxonRelationship.getUuid() + "): " + e.getMessage());
249 						}
250 					}
251 				} else if (taxa.size() > 1) {
252 					logger.error("TaxonRelationship could not be created. This TaxonNode has " + taxa.size() + " Taxa: " + childNodeTaxon.getUuid() + " (" + childNodeTaxon.getTitleCache() + ")");
253 				}
254 				
255 				// TaxonNameRelationships
256 				Set<NameRelationship> nameRelations = childNodeTaxonName.getRelationsFromThisName();
257 				for (NameRelationship nameRelation : nameRelations) {
258 					try {
259 						if (neededValuesNotNull(nameRelation, state)) {
260 							doCount(count++, modCount, pluralString);
261 							success &= mapping.invoke(nameRelation);
262 						}
263 					} catch (SQLException e) {
264 						logger.error("NameRelationship could not be created: " + e.getMessage());
265 					}
266 				}
267 
268 			}
269 			
270 			// SynonymRelationships
271 			Set<Synonym> synonyms = childNodeTaxon.getSynonyms(); // synonyms of accepted taxon
272 			for (Synonym synonym : synonyms) {
273 				TaxonNameBase synonymTaxonName = synonym.getName();
274 				
275 				// Store synonym data in Taxon table
276 				invokeSynonyms(state, synonymTaxonName);
277 
278 				Set<SynonymRelationship> synonymRelations = synonym.getSynonymRelations();
279 				for (SynonymRelationship synonymRelationship : synonymRelations) {
280 					try {
281 						if (neededValuesNotNull(synonymRelationship, state)) {
282 							doCount(count++, modCount, pluralString);
283 							success &= mapping.invoke(synonymRelationship);
284 							
285 						}
286 					} catch (SQLException e) {
287 						logger.error("SynonymRelationship could not be created for this SynonymRelation (" + synonymRelationship.getUuid() + "): " + e.getMessage());
288 					}
289 				}
290 
291 				// SynonymNameRelationship
292 				Set<NameRelationship> nameRelations = synonymTaxonName.getRelationsFromThisName();
293 				for (NameRelationship nameRelation : nameRelations) {
294 					try {
295 						if (neededValuesNotNull(nameRelation, state)) {
296 							doCount(count++, modCount, pluralString);
297 							success &= mapping.invoke(nameRelation);
298 						}
299 					} catch (SQLException e) {
300 						logger.error("NameRelationship could not be created for this NameRelation (" + nameRelation.getUuid() + "): " + e.getMessage());
301 					}
302 				}
303 
304 			}
305 			
306 		}
307 		
308 	}
309 
310 	/**
311 	 * Determines synonym related data and saves them.
312 	 * @param state
313 	 * @param sr
314 	 */
315 	private static void invokeSynonyms(PesiExportState state, TaxonNameBase synonymTaxonName) {
316 		// Store KingdomFk and Rank information in Taxon table
317 		Integer kingdomFk = PesiTransformer.nomenClaturalCode2Kingdom(nomenclaturalCode);
318 		Integer synonymFk = state.getDbId(synonymTaxonName);
319 
320 		saveSynonymData(synonymTaxonName, nomenclaturalCode, kingdomFk, synonymFk);
321 	}
322 
323 	/**
324 	 * Stores synonym data.
325 	 * @param taxonName
326 	 * @param nomenclaturalCode
327 	 * @param kingdomFk
328 	 * @param synonymParentTaxonFk
329 	 * @param currentTaxonFk
330 	 */
331 	private static boolean saveSynonymData(TaxonNameBase taxonName,
332 			NomenclaturalCode nomenclaturalCode, Integer kingdomFk,
333 			Integer currentSynonymFk) {
334 		try {
335 			if (kingdomFk != null) {
336 				synonymsStmt.setInt(1, kingdomFk);
337 			} else {
338 				synonymsStmt.setObject(1, null);
339 			}
340 			
341 			Integer rankFk = getRankFk(taxonName, nomenclaturalCode);
342 			if (rankFk != null) {
343 				synonymsStmt.setInt(2, rankFk);
344 			} else {
345 				synonymsStmt.setObject(2, null);
346 			}
347 			synonymsStmt.setString(3, getRankCache(taxonName, nomenclaturalCode));
348 			
349 			if (currentSynonymFk != null) {
350 				synonymsStmt.setInt(4, currentSynonymFk);
351 			} else {
352 				synonymsStmt.setObject(4, null);
353 			}
354 			synonymsStmt.executeUpdate();
355 			return true;
356 		} catch (SQLException e) {
357 			logger.error("SQLException during invoke for taxonName - " + taxonName.getUuid() + " (" + taxonName.getTitleCache() + "): " + e.getMessage());
358 			e.printStackTrace();
359 			return false;
360 		}
361 	}
362 
363 	/**
364 	 * Checks whether needed values for an entity are NULL.
365 	 * @return
366 	 */
367 	private boolean neededValuesNotNull(RelationshipBase<?, ?, ?> relationship, PesiExportState state) {
368 		boolean result = true;
369 		if (getTaxonFk1(relationship, state) == null) {
370 			logger.error("TaxonFk1 is NULL, but is not allowed to be. Therefore no record was written to export database for this relationship: " + relationship.getUuid());
371 			result = false;
372 		}
373 		if (getTaxonFk2(relationship, state) == null) {
374 			logger.error("TaxonFk2 is NULL, but is not allowed to be. Therefore no record was written to export database for this relationship: " + relationship.getUuid());
375 			result = false;
376 		}
377 		return result;
378 	}
379 	
380 	/**
381 	 * Deletes all entries of database tables related to <code>RelTaxon</code>.
382 	 * @param state The {@link PesiExportState PesiExportState}.
383 	 * @return Whether the delete operation was successful or not.
384 	 */
385 	protected boolean doDelete(PesiExportState state) {
386 		PesiExportConfigurator pesiConfig = (PesiExportConfigurator) state.getConfig();
387 		
388 		String sql;
389 		Source destination =  pesiConfig.getDestination();
390 
391 		// Clear RelTaxon
392 		sql = "DELETE FROM " + dbTableName;
393 		destination.setQuery(sql);
394 		destination.update(sql);
395 		return true;
396 	}
397 
398 	/* (non-Javadoc)
399 	 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#isIgnore(eu.etaxonomy.cdm.io.common.IoStateBase)
400 	 */
401 	@Override
402 	protected boolean isIgnore(PesiExportState state) {
403 		return ! state.getConfig().isDoRelTaxa();
404 	}
405 
406 	/**
407 	 * Returns the <code>TaxonFk1</code> attribute. It corresponds to a CDM <code>TaxonRelationship</code>.
408 	 * @param relationship The {@link RelationshipBase Relationship}.
409 	 * @param state The {@link PesiExportState PesiExportState}.
410 	 * @return The <code>TaxonFk1</code> attribute.
411 	 * @see MethodMapper
412 	 */
413 	private static Integer getTaxonFk1(RelationshipBase<?, ?, ?> relationship, PesiExportState state) {
414 		return getObjectFk(relationship, state, true);
415 	}
416 	
417 	/**
418 	 * Returns the <code>TaxonFk2</code> attribute. It corresponds to a CDM <code>SynonymRelationship</code>.
419 	 * @param relationship The {@link RelationshipBase Relationship}.
420 	 * @param state The {@link PesiExportState PesiExportState}.
421 	 * @return The <code>TaxonFk2</code> attribute.
422 	 * @see MethodMapper
423 	 */
424 	private static Integer getTaxonFk2(RelationshipBase<?, ?, ?> relationship, PesiExportState state) {
425 		return getObjectFk(relationship, state, false);
426 	}
427 	
428 	/**
429 	 * Returns the <code>RelTaxonQualifierFk</code> attribute.
430 	 * @param relationship The {@link RelationshipBase Relationship}.
431 	 * @return The <code>RelTaxonQualifierFk</code> attribute.
432 	 * @see MethodMapper
433 	 */
434 	@SuppressWarnings("unused")
435 	private static Integer getRelTaxonQualifierFk(RelationshipBase<?, ?, ?> relationship) {
436 		return PesiTransformer.taxonRelation2RelTaxonQualifierFk(relationship);
437 	}
438 	
439 	/**
440 	 * Returns the <code>RelQualifierCache</code> attribute.
441 	 * @param relationship The {@link RelationshipBase Relationship}.
442 	 * @return The <code>RelQualifierCache</code> attribute.
443 	 * @see MethodMapper
444 	 */
445 	@SuppressWarnings("unused")
446 	private static String getRelQualifierCache(RelationshipBase<?, ?, ?> relationship) {
447 		String result = null;
448 		if (nomenclaturalCode != null) {
449 			if (nomenclaturalCode.equals(NomenclaturalCode.ICZN)) {
450 				result = PesiTransformer.zoologicalTaxonRelation2RelTaxonQualifierCache(relationship);
451 			} else {
452 				result = PesiTransformer.taxonRelation2RelTaxonQualifierCache(relationship);
453 			}
454 		} else {
455 			logger.error("NomenclaturalCode is NULL while creating the following relationship: " + relationship.getUuid());
456 		}
457 		return result;
458 	}
459 	
460 	/**
461 	 * Returns the <code>Notes</code> attribute.
462 	 * @param relationship The {@link RelationshipBase Relationship}.
463 	 * @return The <code>Notes</code> attribute.
464 	 * @see MethodMapper
465 	 */
466 	@SuppressWarnings("unused")
467 	private static String getNotes(RelationshipBase<?, ?, ?> relationship) {
468 		// TODO
469 		return null;
470 	}
471 
472 	/**
473 	 * Returns the database key of an object in the given relationship.
474 	 * @param relationship {@link RelationshipBase RelationshipBase}.
475 	 * @param state {@link PesiExportState PesiExportState}.
476 	 * @param isFrom A boolean value indicating whether the database key of the parent or child in this relationship is searched. <code>true</code> means the child is searched. <code>false</code> means the parent is searched.
477 	 * @return The database key of an object in the given relationship.
478 	 */
479 	private static Integer getObjectFk(RelationshipBase<?, ?, ?> relationship, PesiExportState state, boolean isFrom) {
480 		TaxonBase<?> taxon = null;
481 		if (relationship.isInstanceOf(TaxonRelationship.class)) {
482 			TaxonRelationship tr = (TaxonRelationship)relationship;
483 			taxon = (isFrom) ? tr.getFromTaxon():  tr.getToTaxon();
484 		} else if (relationship.isInstanceOf(SynonymRelationship.class)) {
485 			SynonymRelationship sr = (SynonymRelationship)relationship;
486 			taxon = (isFrom) ? sr.getSynonym() : sr.getAcceptedTaxon();
487 		} else if (relationship.isInstanceOf(NameRelationship.class)) {
488 			NameRelationship nr = (NameRelationship)relationship;
489 			TaxonNameBase taxonName = (isFrom) ? nr.getFromName() : nr.getToName();
490 			return state.getDbId(taxonName);
491 		}
492 		if (taxon != null) {
493 			return state.getDbId(taxon.getName());
494 		}
495 		logger.warn("No taxon found in state for relationship: " + relationship.toString());
496 		return null;
497 	}
498 
499 	/**
500 	 * Returns the <code>RankFk</code> attribute.
501 	 * @param taxonName The {@link TaxonNameBase TaxonName}.
502 	 * @param nomenclaturalCode The {@link NomenclaturalCode NomenclaturalCode}.
503 	 * @return The <code>RankFk</code> attribute.
504 	 * @see MethodMapper
505 	 */
506 	private static Integer getRankFk(TaxonNameBase taxonName, NomenclaturalCode nomenclaturalCode) {
507 		Integer result = null;
508 		if (nomenclaturalCode != null) {
509 			if (taxonName != null && taxonName.getRank() == null) {
510 				logger.warn("Rank is null: " + taxonName.getUuid() + " (" + taxonName.getTitleCache() + ")");
511 			}
512 			result = PesiTransformer.rank2RankId(taxonName.getRank(), PesiTransformer.nomenClaturalCode2Kingdom(nomenclaturalCode));
513 			if (result == null) {
514 				logger.warn("Rank could not be determined for PESI-Kingdom-Id " + PesiTransformer.nomenClaturalCode2Kingdom(nomenclaturalCode) + " and TaxonName " + taxonName.getUuid() + " (" + taxonName.getTitleCache() + ")");
515 			}
516 		}
517 		return result;
518 	}
519 
520 	/**
521 	 * Returns the <code>RankCache</code> attribute.
522 	 * @param taxonName The {@link TaxonNameBase TaxonName}.
523 	 * @param nomenclaturalCode The {@link NomenclaturalCode NomenclaturalCode}.
524 	 * @return The <code>RankCache</code> attribute.
525 	 * @see MethodMapper
526 	 */
527 	private static String getRankCache(TaxonNameBase taxonName, NomenclaturalCode nomenclaturalCode) {
528 		String result = null;
529 		if (nomenclaturalCode != null) {
530 			result = PesiTransformer.rank2RankCache(taxonName.getRank(), PesiTransformer.nomenClaturalCode2Kingdom(nomenclaturalCode));
531 		}
532 		return result;
533 	}
534 
535 	/**
536 	 * Returns the CDM to PESI specific export mappings.
537 	 * @return The {@link PesiExportMapping PesiExportMapping}.
538 	 */
539 	private PesiExportMapping getMapping() {
540 		PesiExportMapping mapping = new PesiExportMapping(dbTableName);
541 		
542 		mapping.addMapper(MethodMapper.NewInstance("TaxonFk1", this.getClass(), "getTaxonFk1", standardMethodParameter, PesiExportState.class));
543 		mapping.addMapper(MethodMapper.NewInstance("TaxonFk2", this.getClass(), "getTaxonFk2", standardMethodParameter, PesiExportState.class));
544 		mapping.addMapper(MethodMapper.NewInstance("RelTaxonQualifierFk", this));
545 		mapping.addMapper(MethodMapper.NewInstance("RelQualifierCache", this));
546 		mapping.addMapper(MethodMapper.NewInstance("Notes", this));
547 
548 		return mapping;
549 	}
550 
551 }