changed NonViralName to base TaxonNameBase to avoid deproxy
[cdmlib.git] / cdmlib-remote / src / main / java / eu / etaxonomy / cdm / remote / controller / NameCatalogueController.java
1
2 package eu.etaxonomy.cdm.remote.controller;
3
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.PrintWriter;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.Iterator;
13 import java.util.LinkedHashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.TreeMap;
18 import java.util.UUID;
19
20 import javax.servlet.http.HttpServletRequest;
21 import javax.servlet.http.HttpServletResponse;
22
23 import java.util.Hashtable;
24
25 import org.springframework.beans.factory.annotation.Autowired;
26 import org.springframework.context.ResourceLoaderAware;
27 import org.springframework.core.io.Resource;
28 import org.springframework.core.io.ResourceLoader;
29 import org.springframework.stereotype.Controller;
30 import org.springframework.web.bind.annotation.RequestMapping;
31 import org.springframework.web.bind.annotation.RequestMethod;
32 import org.springframework.web.bind.annotation.RequestParam;
33 import org.springframework.web.servlet.ModelAndView;
34
35 import eu.etaxonomy.cdm.api.service.IClassificationService;
36 import eu.etaxonomy.cdm.api.service.INameService;
37 import eu.etaxonomy.cdm.api.service.ITaxonService;
38 import eu.etaxonomy.cdm.common.DocUtils;
39
40 import eu.etaxonomy.cdm.remote.dto.common.ErrorResponse;
41 import eu.etaxonomy.cdm.remote.dto.common.RemoteResponse;
42 import eu.etaxonomy.cdm.remote.dto.namecatalogue.NameInformation;
43 import eu.etaxonomy.cdm.remote.dto.namecatalogue.NameSearch;
44 import eu.etaxonomy.cdm.remote.dto.namecatalogue.TaxonInformation;
45 import eu.etaxonomy.cdm.remote.view.HtmlView;
46 import eu.etaxonomy.cdm.model.common.CdmBase;
47 import eu.etaxonomy.cdm.model.common.Language;
48 import eu.etaxonomy.cdm.model.name.NonViralName;
49 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
50 import eu.etaxonomy.cdm.model.reference.Reference;
51 import eu.etaxonomy.cdm.model.taxon.Classification;
52 import eu.etaxonomy.cdm.model.taxon.Synonym;
53 import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
54 import eu.etaxonomy.cdm.model.taxon.Taxon;
55 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
56 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
57 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
58 import eu.etaxonomy.cdm.persistence.query.MatchMode;
59 import eu.etaxonomy.cdm.persistence.query.OrderHint;
60 import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
61
62 /**
63 * The controller class for the namespace 'name_catalogue'. This web service namespace
64 * is an add-on to the already existing CDM REST API and provides information relating
65 * to scientific names as well as taxa present in the underlying datasource.
66 *
67 * @author c.mathew
68 * @version 1.1.0
69 * @created 15-Apr-2012
70 */
71
72 @Controller
73 @RequestMapping(value = { "/name_catalogue" })
74 public class NameCatalogueController extends BaseController<TaxonNameBase, INameService> implements ResourceLoaderAware {
75
76 private ResourceLoader resourceLoader;
77
78 /** Taxonomic status 'accepted' string */
79 public static final String ACCEPTED_NAME_STATUS = "accepted";
80
81 /** Taxonpmic status 'synonym' string */
82 public static final String SYNONYM_STATUS = "synonym";
83
84 /** Flag 'doubtful' strings */
85 public static final String DOUBTFUL_FLAG = "doubtful";
86
87 /** Base scientific name search type */
88 public static final String NAME_SEARCH = "name";
89
90 /** Complete scientific name search type */
91 public static final String TITLE_SEARCH = "title";
92
93 /** Default name search type */
94 public static final String DEFAULT_SEARCH_TYPE = NAME_SEARCH;
95
96 /** Classifcation 'default' key */
97 public static final String CLASSIFICATION_DEFAULT = "default";
98
99 /** Classifcation 'all' key */
100 public static final String CLASSIFICATION_ALL = "all";
101
102 @Autowired
103 private ITaxonService taxonService;
104
105 @Autowired
106 private IClassificationService classificationService;
107
108 /** Hibernate name search initialisation strategy */
109 private static final List<String> NAME_SEARCH_INIT_STRATEGY = Arrays.asList(new String[] {
110 "combinationAuthorTeam.$",
111 "exCombinationAuthorTeam.$",
112 "basionymAuthorTeam.$",
113 "exBasionymAuthorTeam.$",
114 "nameCache",
115 "taxonBases",
116 "taxonBases.synonymRelations.type.$"});
117
118 /** Hibernate name information initialisation strategy */
119 private static final List<String> NAME_INFORMATION_INIT_STRATEGY = Arrays.asList(new String[] {
120 "taxonBases",
121 "status",
122 "nomenclaturalReference.$",
123 "combinationAuthorTeam.$",
124 "exCombinationAuthorTeam.$",
125 "basionymAuthorTeam.$",
126 "exBasionymAuthorTeam.$",
127 "relationsToThisName.fromName.$",
128 "relationsToThisName.nomenclaturalReference.$",
129 "relationsToThisName.type.$",
130 "relationsFromThisName.toName.$",
131 "relationsFromThisName.nomenclaturalReference.$",
132 "relationsFromThisName.type.$"});
133
134 /** Hibernate taxon information initialisation strategy */
135 private static final List<String> TAXON_INFORMATION_INIT_STRATEGY = Arrays.asList(new String[] {
136 "name.titleCache",
137 "name.rank.titleCache",
138 "synonymRelations.synonym.name.rank.titleCache",
139 "synonymRelations.acceptedTaxon.name.rank.titleCache",
140 "synonymRelations.type.$",
141 "taxonRelations.toTaxon.$",
142 "taxonRelations.fromTaxon.$",
143 "taxonRelations.type.$",
144 "synonymRelations.relatedTo.name.rank.titleCache",
145 "relationsFromThisTaxon.type.$",
146 "relationsFromThisTaxon.relatedTo.name.rank.titleCache",
147 "relationsToThisTaxon.type.$",
148 "relationsToThisTaxon.relatedFrom.name.rank.titleCache",
149 "taxonNodes",
150 "taxonNodes.classification" });
151
152 /** Hibernate taxon node initialisation strategy */
153 private static final List<String> TAXON_NODE_INIT_STRATEGY = Arrays.asList(new String[] {
154 "taxon.sec",
155 "taxon.name",
156 "classification",
157 "classification.reference.$",
158 "classification.reference.authorTeam.$" });
159
160 /** Hibernate classification vocabulary initialisation strategy */
161 private static final List<String> VOC_CLASSIFICATION_INIT_STRATEGY = Arrays.asList(new String[] {
162 "classification",
163 "classification.reference.$",
164 "classification.reference.authorTeam.$" });
165 public NameCatalogueController() {
166 super();
167 setInitializationStrategy(Arrays.asList(new String[] { "$" }));
168 }
169
170 /*
171 * (non-Javadoc)
172 *
173 * @see
174 * eu.etaxonomy.cdm.remote.controller.GenericController#setService(eu
175 * .etaxonomy.cdm.api.service.IService)
176 */
177 @Autowired
178 @Override
179 public void setService(INameService service) {
180 this.service = service;
181 }
182
183 /**
184 * Returns a documentation page for the Name Search API.
185 * <p>
186 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
187 *
188 * @param request
189 * @param response
190 * @return Html page describing the Name Search API
191 * @throws IOException
192 */
193 @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {})
194 public ModelAndView doGetNameSearchDocumentation(
195 HttpServletRequest request, HttpServletResponse response)
196 throws IOException {
197 ModelAndView mv = new ModelAndView();
198 // Read apt documentation file.
199 Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-default.apt");
200 // using input stream as this works for both files in the classes directory
201 // as well as files inside jars
202 InputStream aptInputStream = resource.getInputStream();
203 // Build Html View
204 Map<String, String> modelMap = new HashMap<String, String>();
205 // Convert Apt to Html
206 modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
207 mv.addAllObjects(modelMap);
208
209 HtmlView hv = new HtmlView();
210 mv.setView(hv);
211 return mv;
212 }
213
214 /**
215 * Returns a list of scientific names matching the <code>{query}</code>
216 * string pattern. Each of these scientific names is accompanied by a list of
217 * name uuids, a list of taxon uuids, a list of accepted taxon uuids, etc.
218 * <p>
219 * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-default.html">here</a>
220 * <p>
221 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
222 *
223 * @param queries
224 * The base scientific name pattern(s) to query for. The query can
225 * contain wildcard characters ('*'). The query can be
226 * performed with no wildcard or with the wildcard at the
227 * begin and / or end depending on the search pattern.
228 * @param request Http servlet request.
229 * @param response Http servlet response.
230 * @return a list of {@link NameSearch} objects each corresponding to a
231 * single query. These are built from {@link TaxonNameBase}
232 * entities which are in turn initialized using
233 * the {@link #NAME_SEARCH_INIT_STRATEGY}
234 * @throws IOException
235 */
236 @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {"query"})
237 public ModelAndView doGetNameSearch(@RequestParam(value = "query", required = true) String[] queries,
238 HttpServletRequest request, HttpServletResponse response) throws IOException {
239 return doGetNameSearch(queries, DEFAULT_SEARCH_TYPE, request, response);
240 }
241
242 /**
243 * Returns a list of scientific names matching the <code>{query}</code>
244 * string pattern. Each of these scientific names is accompanied by a list of
245 * name uuids, a list of taxon uuids and a list of accepted taxon uuids.
246 * <p>
247 * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-default.html">here</a>
248 * <p>
249 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
250 *
251 * @param query
252 * The scientific name pattern(s) to query for. The query can
253 * contain wildcard characters ('*'). The query can be
254 * performed with no wildcard or with the wildcard at the
255 * begin and / or end depending on the search pattern.
256 * @param type
257 * The type of name to query. This be either
258 * "name" : scientific name corresponding to 'name cache' in CDM or
259 * "title" : complete name corresponding to 'title cache' in CDM
260 * @param request Http servlet request.
261 * @param response Http servlet response.
262 * @return a List of {@link NameSearch} objects each corresponding to a
263 * single query. These are built from {@link TaxonNameBase} entities
264 * which are in turn initialized using the {@link #NAME_SEARCH_INIT_STRATEGY}
265 * @throws IOException
266 */
267 @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {"query", "type"})
268 public ModelAndView doGetNameSearch(@RequestParam(value = "query", required = true) String[] queries,
269 @RequestParam(value = "type", required = false, defaultValue = DEFAULT_SEARCH_TYPE) String searchType,
270 HttpServletRequest request, HttpServletResponse response) throws IOException {
271 ModelAndView mv = new ModelAndView();
272 List<RemoteResponse> nsList = new ArrayList<RemoteResponse>();
273
274 // if search type is not known then return error
275 if (!searchType.equals(NAME_SEARCH) && !searchType.equals(TITLE_SEARCH)) {
276 ErrorResponse er = new ErrorResponse();
277 er.setErrorMessage("searchType parameter can only be set as" + NAME_SEARCH + " or "
278 + TITLE_SEARCH);
279 mv.addObject(er);
280 return mv;
281 }
282
283 // search through each query
284 for (String query : queries) {
285
286 String queryWOWildcards = getQueryWithoutWildCards(query);
287 MatchMode mm = getMatchModeFromQuery(query);
288 logger.info("doGetNameSearch()" + request.getServletPath() + " for query \"" + query
289 + "\" without wild cards : " + queryWOWildcards + " and match mode : " + mm);
290 List<NonViralName> nameList = new ArrayList<NonViralName>();
291
292 // if "name" search then find by name cache
293 if (searchType.equals(NAME_SEARCH)) {
294 nameList = (List<NonViralName>) service.findNamesByNameCache(queryWOWildcards, mm,
295 NAME_SEARCH_INIT_STRATEGY);
296 }
297
298 //if "title" search then find by title cache
299 if (searchType.equals(TITLE_SEARCH)) {
300 nameList = (List<NonViralName>) service.findNamesByTitleCache(queryWOWildcards, mm,
301 NAME_SEARCH_INIT_STRATEGY);
302 }
303
304 // if search is successful then get related information , else return error
305 if (nameList == null || !nameList.isEmpty()) {
306 NameSearch ns = new NameSearch();
307 ns.setRequest(query);
308
309 for (NonViralName nvn : nameList) {
310 // we need to retrieve both taxon uuid of name queried and
311 // the corresponding accepted taxa.
312 // reason to return accepted taxa also, is to be able to get from
313 // scientific name to taxon concept in two web service calls.
314 Set<TaxonBase> tbSet = nvn.getTaxonBases();
315 Set<TaxonBase> accTbSet = new HashSet<TaxonBase>();
316 for (TaxonBase tb : tbSet) {
317 // if synonym then get accepted taxa.
318 if (tb instanceof Synonym) {
319 Synonym synonym = (Synonym) tb;
320 Set<SynonymRelationship> synRelationships = synonym.getSynonymRelations();
321 for (SynonymRelationship sr : synRelationships) {
322 Taxon accTaxon = sr.getAcceptedTaxon();
323 accTbSet.add(accTaxon);
324 }
325 } else {
326 accTbSet.add(tb);
327 }
328 }
329 // update name search object
330 ns.addToResponseList(nvn.getTitleCache(), nvn.getNameCache(), nvn.getUuid()
331 .toString(), tbSet, accTbSet);
332 }
333 nsList.add(ns);
334
335 } else {
336 ErrorResponse er = new ErrorResponse();
337 er.setErrorMessage("No Taxon Name for given query : " + query);
338 nsList.add(er);
339 }
340 }
341 mv.addObject(nsList);
342 return mv;
343 }
344
345
346 /**
347 * Returns a documentation page for the Name Information API.
348 * <p>
349 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/name</b>
350 *
351 * @param request Http servlet request.
352 * @param response Http servlet response.
353 * @return Html page describing the Name Information API
354 * @throws IOException
355 */
356 @RequestMapping(value = { "name" }, method = RequestMethod.GET, params = {})
357 public ModelAndView doGetNameInformationDocumentation(
358 HttpServletRequest request, HttpServletResponse response)
359 throws IOException {
360 ModelAndView mv = new ModelAndView();
361 // Read apt documentation file.
362 Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-name-info.apt");
363 // using input stream as this works for both files in the classes directory
364 // as well as files inside jars
365 InputStream aptInputStream = resource.getInputStream();
366 // Build Html View
367 Map<String, String> modelMap = new HashMap<String, String>();
368 // Convert Apt to Html
369 modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
370 mv.addAllObjects(modelMap);
371
372 HtmlView hv = new HtmlView();
373 mv.setView(hv);
374 return mv;
375 }
376
377 /**
378 * Returns information related to the scientific name matching the given
379 * <code>{nameUuid}</code>. The information includes the name string,
380 * relationships, rank, list of related lsids / taxon uuids, etc.
381 * <p>
382 * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-name-info.html">here</a>
383 * <p>
384 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/name</b>
385 *
386 * @param nameUuids uuid(s) of the scientific name to search for.
387 * @param request Http servlet request.
388 * @param response Http servlet response.
389 * @return a List of {@link NameInformation} objects each corresponding to a
390 * single name uuid. These are built from {@link TaxonNameBase} entities
391 * which are in turn initialized using the {@link #NAME_INFORMATION_INIT_STRATEGY}
392 * @throws IOException
393 */
394 @RequestMapping(value = { "name" }, method = RequestMethod.GET, params = {"nameUuid"})
395 public ModelAndView doGetNameInformation(@RequestParam(value = "nameUuid", required = true) String[] nameUuids,
396 HttpServletRequest request, HttpServletResponse response) throws IOException {
397 ModelAndView mv = new ModelAndView();
398 List<RemoteResponse> niList = new ArrayList<RemoteResponse>();
399 // loop through each name uuid
400 for (String nameUuid : nameUuids) {
401 logger.info("doGetNameInformation()" + request.getServletPath() + " for name uuid \""
402 + nameUuid + "\"");
403 // find name by uuid
404 NonViralName nvn = (NonViralName) service.findNameByUuid(UUID.fromString(nameUuid),
405 NAME_INFORMATION_INIT_STRATEGY);
406
407 // if search is successful then get related information, else return error
408 if (nvn != null) {
409 NameInformation ni = new NameInformation();
410 ni.setRequest(nameUuid);
411 Reference ref = (Reference) nvn.getNomenclaturalReference();
412 String citation = "";
413 String citation_details = "";
414 if (ref != null) {
415 citation = ref.getTitleCache();
416 }
417 // update name information object
418 ni.setResponse(nvn.getTitleCache(), nvn.getNameCache(), nvn.getRank().getTitleCache(),
419 nvn.getStatus(), citation, nvn.getRelationsFromThisName(),
420 nvn.getRelationsToThisName(), nvn.getTaxonBases());
421 niList.add(ni);
422 } else {
423 ErrorResponse re = new ErrorResponse();
424
425 if(isValid(nameUuid)) {
426 re.setErrorMessage("No Name for given UUID : " + nameUuid);
427 } else {
428 re.setErrorMessage(nameUuid + " not a valid UUID");
429 }
430 niList.add(re);
431 }
432 }
433 mv.addObject(niList);
434 return mv;
435 }
436
437 /**
438 * Returns a documentation page for the Taxon Information API.
439 * <p>
440 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/taxon</b>
441 *
442 * @param request Http servlet request.
443 * @param response Http servlet response.
444 * @return Html page describing the Taxon Information API
445 * @throws IOException
446 */
447 @RequestMapping(value = { "taxon" }, method = RequestMethod.GET, params = {})
448 public ModelAndView doGetTaxonInformation(
449 HttpServletRequest request, HttpServletResponse response)
450 throws IOException {
451 ModelAndView mv = new ModelAndView();
452 // Read apt documentation file.
453 Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-taxon-info.apt");
454 // using input stream as this works for both files in the classes directory
455 // as well as files inside jars
456 InputStream aptInputStream = resource.getInputStream();
457 // Build Html View
458 Map<String, String> modelMap = new HashMap<String, String>();
459 // Convert Apt to Html
460 modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
461 mv.addAllObjects(modelMap);
462
463 HtmlView hv = new HtmlView();
464 mv.setView(hv);
465 return mv;
466 }
467
468 /**
469 * Returns information related to the taxon matching the given
470 * <code>{taxonUuid}</code>. The information includes the name cache, title cache
471 * relationship type, taxonomic status, information , etc.
472 * <p>
473 * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-taxon-info.html">here</a>
474 * <p>
475 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
476 *
477 * @param taxonUuid
478 * The taxon uuid to query for. The classification returned corresponds
479 * to the first in the alphabetically sorted list of classifications
480 * currently available in the database.
481 *
482 * @param request Http servlet request.
483 * @param response Http servlet response.
484 * @return a List of {@link TaxonInformation} objects each corresponding to a
485 * single query. These are built from {@TaxonBase} entities which are
486 * in turn initialized using the {@link #TAXON_INFORMATION_INIT_STRATEGY}
487 * @throws IOException
488 */
489 @RequestMapping(value = { "taxon" }, method = RequestMethod.GET,params = {"taxonUuid"})
490 public ModelAndView doGetTaxonInformation(
491 @RequestParam(value = "taxonUuid", required = true) String[] taxonUuids,
492 HttpServletRequest request, HttpServletResponse response) throws IOException {
493 return doGetTaxonInformation(taxonUuids,CLASSIFICATION_DEFAULT, request, response);
494 }
495
496 /**
497 * Returns information related to the taxon matching the given
498 * <code>{taxonUuid}</code>. The information includes the name cache, title cache
499 * relationship type, taxonomic status, information , etc.
500 * <p>
501 * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-taxon-info.html">here</a>
502 * <p>
503 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/taxon</b>
504 *
505 * @param taxonUuid
506 * The taxon uuid to query for.
507 * @param classification
508 * [Optional] String representing the taxonomic classification to use for
509 * building the classification tree. Defaults to the first in the alphabetically
510 * sorted list of classifications currently available in the database.
511 *
512 * @param request Http servlet request.
513 * @param response Http servlet response.
514 * @return a List of {@link TaxonInformation} objects each corresponding to a
515 * single query. These are built from {@TaxonBase} entities which are
516 * in turn initialized using the {@link #TAXON_INFORMATION_INIT_STRATEGY}
517 * @throws IOException
518 */
519 @RequestMapping(value = { "taxon" }, method = RequestMethod.GET, params = {"taxonUuid", "classification"})
520 public ModelAndView doGetTaxonInformation(
521 @RequestParam(value = "taxonUuid", required = true) String[] taxonUuids,
522 @RequestParam(value = "classification", required = false, defaultValue = CLASSIFICATION_DEFAULT) String classificationType,
523 HttpServletRequest request, HttpServletResponse response) throws IOException {
524 ModelAndView mv = new ModelAndView();
525 List<RemoteResponse> tiList = new ArrayList<RemoteResponse>();
526 // loop through each name uuid
527 for (String taxonUuid : taxonUuids) {
528 logger.info("doGetTaxonInformation()" + request.getServletPath() + " for taxon uuid \""
529 + taxonUuid);
530 // find name by uuid
531 TaxonBase tb = taxonService.findTaxonByUuid(UUID.fromString(taxonUuid),
532 TAXON_INFORMATION_INIT_STRATEGY);
533 // if search is successful then get related information, else return error
534 if (tb != null) {
535 TaxonInformation ti = new TaxonInformation();
536 ti.setRequest(taxonUuid);
537 // check if result (taxon base) is a taxon or synonym
538 if (tb.isInstanceOf(Taxon.class)) {
539 Taxon taxon = (Taxon) tb;
540 // build classification map
541 Map classificationMap = getClassification(taxon, classificationType);
542
543 logger.info("taxon uuid " + taxon.getUuid().toString() + " original hash code : " + System.identityHashCode(taxon) + ", name class " + taxon.getName().getClass().getName());
544 // update taxon information object with taxon related data
545 NonViralName nvn = (NonViralName) taxon.getName();
546 ti.setResponseTaxon(tb.getTitleCache(),
547 nvn.getTitleCache(),
548 nvn.getRank().getTitleCache(),
549 ACCEPTED_NAME_STATUS,
550 buildFlagMap(tb),
551 classificationMap);
552
553 Set<SynonymRelationship> synRelationships = taxon.getSynonymRelations();
554 // add synonyms (if exists) to taxon information object
555 for (SynonymRelationship sr : synRelationships) {
556 Synonym syn = sr.getSynonym();
557 String uuid = syn.getUuid().toString();
558 String title = syn.getTitleCache();
559 TaxonNameBase synnvn = (TaxonNameBase) syn.getName();
560 String name = synnvn.getTitleCache();
561 String rank = synnvn.getRank().getTitleCache();
562 String status = SYNONYM_STATUS;
563 String relLabel = sr.getType()
564 .getInverseRepresentation(Language.DEFAULT())
565 .getLabel();
566 ti.addToResponseRelatedTaxa(uuid, title, name, rank, status, "", relLabel);
567 }
568
569 // build relationship information as,
570 // - relationships from the requested taxon
571 Set<TaxonRelationship> trFromSet = taxon.getRelationsFromThisTaxon();
572 for (TaxonRelationship tr : trFromSet) {
573 String titleTo = tr.getToTaxon().getTitleCache();
574 TaxonNameBase tonvn = (TaxonNameBase) tr.getToTaxon().getName();
575 String name = tonvn.getTitleCache();
576 String rank = tonvn.getRank().getTitleCache();
577 String uuid = tr.getToTaxon().getUuid().toString();
578 String status = ACCEPTED_NAME_STATUS;
579 String relLabel = tr.getType().getRepresentation(Language.DEFAULT())
580 .getLabel();
581 ti.addToResponseRelatedTaxa(uuid, titleTo, name, rank, status, "", relLabel);
582 }
583
584 // - relationships from the requested taxon
585 Set<TaxonRelationship> trToSet = taxon.getRelationsToThisTaxon();
586 for (TaxonRelationship tr : trToSet) {
587 String titleFrom = tr.getFromTaxon().getTitleCache();
588 TaxonNameBase fromnvn = (TaxonNameBase) tr.getToTaxon().getName();
589 String name = fromnvn.getTitleCache();
590 String rank = fromnvn.getRank().getTitleCache();
591 String uuid = tr.getFromTaxon().getUuid().toString();
592 String status = ACCEPTED_NAME_STATUS;
593 String relLabel = tr.getType()
594 .getInverseRepresentation(Language.DEFAULT())
595 .getLabel();
596 ti.addToResponseRelatedTaxa(uuid, titleFrom, name, rank, status, "", relLabel);
597 }
598 } else if (tb instanceof Synonym) {
599 Synonym synonym = (Synonym) tb;
600 TaxonNameBase nvn = (TaxonNameBase) synonym.getName();
601 // update taxon information object with synonym related data
602 ti.setResponseTaxon(synonym.getTitleCache(),
603 nvn.getTitleCache(),
604 nvn.getRank().getTitleCache(),
605 SYNONYM_STATUS,
606 buildFlagMap(synonym),
607 new TreeMap<String,Map>());
608 // add accepted taxa (if exists) to taxon information object
609
610 Set<SynonymRelationship> synRelationships = synonym.getSynonymRelations();
611 for (SynonymRelationship sr : synRelationships) {
612 Taxon accTaxon = sr.getAcceptedTaxon();
613 String uuid = accTaxon.getUuid().toString();
614 logger.info("acc taxon uuid " + accTaxon.getUuid().toString() + " original hash code : " + System.identityHashCode(accTaxon) + ", name class " + accTaxon.getName().getClass().getName());
615 String title = accTaxon.getTitleCache();
616 logger.info("taxon title cache : " + accTaxon.getTitleCache());
617
618 TaxonNameBase accnvn = (TaxonNameBase)accTaxon.getName();
619 String name = accnvn.getTitleCache();
620 String rank = accnvn.getRank().getTitleCache();
621 String status = ACCEPTED_NAME_STATUS;
622 String relLabel = sr.getType().getRepresentation(Language.DEFAULT())
623 .getLabel();
624 ti.addToResponseRelatedTaxa(uuid, title, name, rank, status, "", relLabel);
625 }
626
627 }
628 tiList.add(ti);
629 } else {
630 ErrorResponse re = new ErrorResponse();
631 if(isValid(taxonUuid)) {
632 re.setErrorMessage("No Taxon for given UUID : " + taxonUuid);
633 } else {
634 re.setErrorMessage(taxonUuid + " not a valid UUID");
635 }
636 tiList.add(re);
637 }
638 }
639 mv.addObject(tiList);
640 return mv;
641 }
642
643 /**
644 * Returns a list of all available classifications (with associated referenc information) and the default classification.
645 * <p>
646 * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-classification-info.html">here</a>
647 * <p>
648 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/voc/classification</b>
649 *
650 * @param request Http servlet request.
651 * @param response Http servlet response.
652 * @return a List of {@link Classification} objects represebted as strings.
653 * These are initialized using the {@link #VOC_CLASSIFICATION_INIT_STRATEGY}
654 * @throws IOException
655 */
656 @RequestMapping(value = { "voc/classification" }, method = RequestMethod.GET, params = {})
657 public ModelAndView doGetClassificationMap(HttpServletRequest request, HttpServletResponse response) throws IOException {
658 List<Map> cmapList = new ArrayList<Map>();
659 Map<String, String> classifications = new HashMap<String, String>();
660 ModelAndView mv = new ModelAndView();
661 List<Classification> clist = getClassificationList(100);
662 boolean isFirst = true;
663 Iterator itr = clist.iterator();
664 // loop through all classifications and populate map with
665 // (classificationKey, reference) elements
666 while(itr.hasNext()) {
667 Classification c = (Classification) itr.next();
668 String refTitleCache = "";
669 String classificationKey = removeInternalWhitespace(c.getTitleCache());
670 if(c.getReference() != null) {
671 refTitleCache = c.getReference().getTitleCache();
672 }
673 // default is the first element of the list
674 // always created with the same sorting (DESCENDING)
675 if(isFirst) {
676 Map<String, String> defaultMap = new HashMap<String, String>();
677 defaultMap.put("default", classificationKey);
678 cmapList.add(defaultMap);
679 isFirst = false;
680 }
681 classifications.put(classificationKey, refTitleCache);
682
683 }
684 Map<String, Map> cmap = new HashMap<String, Map>();
685 cmap.put("classification",classifications);
686 cmapList.add(cmap);
687 mv.addObject(cmapList);
688 return mv;
689 }
690
691 /**
692 * Returns the match mode by parsing the input string of wildcards.
693 *
694 * @param query
695 * String to parse.
696 *
697 * @return {@link MatchMode} depending on the the position of the wildcard (*)
698 */
699 private MatchMode getMatchModeFromQuery(String query) {
700 if (query.startsWith("*") && query.endsWith("*")) {
701 return MatchMode.ANYWHERE;
702 } else if (query.startsWith("*")) {
703 return MatchMode.END;
704 } else if (query.endsWith("*")) {
705 return MatchMode.BEGINNING;
706 } else {
707 return MatchMode.EXACT;
708 }
709 }
710
711 /**
712 * Removes wildcards from the input string.
713 *
714 * @param query
715 * String to parse.
716 *
717 * @return input string with wildcards removed
718 */
719 private String getQueryWithoutWildCards(String query) {
720
721 String newQuery = query;
722
723 if (query.startsWith("*")) {
724 newQuery = newQuery.substring(1, newQuery.length());
725 }
726
727 if (query.endsWith("*")) {
728 newQuery = newQuery.substring(0, newQuery.length() - 1);
729 }
730
731 return newQuery.trim();
732 }
733
734 /**
735 * Build map with taxon flag key-value pairs.
736 */
737 private Map<String, String> buildFlagMap(TaxonBase tb) {
738 Map<String, String> flags = new Hashtable<String, String>();
739 flags.put(DOUBTFUL_FLAG, Boolean.toString(tb.isDoubtful()));
740 return flags;
741 }
742
743 /**
744 * Build classification map.
745 */
746 private Map<String, Map> getClassification(Taxon taxon, String classificationType) {
747 // Using TreeMap is important, because we need the sorting of the classification keys
748 // in the map to be stable.
749 TreeMap<String, Map> sourceClassificationMap = buildClassificationMap(taxon, classificationType);
750
751 // if classification key is 'default' then return the default element of the map
752 if(classificationType.equals(CLASSIFICATION_DEFAULT) && !sourceClassificationMap.isEmpty()) {
753 List<Classification> clist = getClassificationList(1);
754 String defaultKey = removeInternalWhitespace(clist.get(0).getTitleCache());
755 return sourceClassificationMap.get(defaultKey);
756 // if classification key is provided then return the classification corresponding to the key
757 } else if(sourceClassificationMap.containsKey(classificationType)) {
758 return sourceClassificationMap.get(classificationType);
759 // if classification key is 'all' then return the entire map
760 } else if(classificationType.equals(CLASSIFICATION_ALL)) {
761 return sourceClassificationMap;
762 } else {
763 return new TreeMap<String,Map>();
764 }
765 }
766
767 /**
768 * Build classification map.
769 */
770 private TreeMap<String, Map> buildClassificationMap(Taxon taxon, String classificationType) {
771 // Using TreeMap is important, because we need the sorting of the classification keys
772 // in the map to be stable.
773 TreeMap<String, Map> sourceClassificationMap = new TreeMap<String, Map>();
774 Set<TaxonNode> taxonNodes = taxon.getTaxonNodes();
775 //loop through taxon nodes and build classification map for each classification key
776 for (TaxonNode tn : taxonNodes) {
777 Map<String, String> classificationMap = new LinkedHashMap<String, String>();
778 List<TaxonNode> tnList = classificationService.loadTreeBranchToTaxon(taxon,
779 tn.getClassification(), null, TAXON_NODE_INIT_STRATEGY);
780 for (TaxonNode classificationtn : tnList) {
781 classificationMap.put(classificationtn.getTaxon().getName().getRank().getTitleCache(),
782 classificationtn.getTaxon().getName().getTitleCache());
783 }
784 String cname = removeInternalWhitespace(tn.getClassification().getTitleCache());
785 logger.info("Building classification map " + cname);
786 sourceClassificationMap.put(cname, classificationMap);
787 }
788 return sourceClassificationMap;
789 }
790
791 private String removeInternalWhitespace(String withWSpace) {
792 String[] words = withWSpace.split("\\s+");
793 // "\\s+" in regular expression language meaning one or
794 // more spaces
795 StringBuilder builder = new StringBuilder();
796 for (String word : words) {
797 builder.append(word);
798 }
799 return builder.toString();
800 }
801
802 private List<Classification> getClassificationList(int limit) {
803 List<OrderHint> orderHints = new ArrayList<OrderHint>();
804 orderHints.add(new OrderHint("titleCache", SortOrder.DESCENDING));
805 List<Classification> clist = classificationService.listClassifications(limit, 0, orderHints, VOC_CLASSIFICATION_INIT_STRATEGY);
806 return clist;
807 }
808
809 private boolean isValid(String uuid){
810 if( uuid == null) return false;
811 try {
812 // we have to convert to object and back to string because the built in fromString does not have
813 // good validation logic.
814
815 UUID fromStringUUID = UUID.fromString(uuid);
816 String toStringUUID = fromStringUUID.toString();
817
818 System.out.println("input uuid : " + uuid + " , parsed uuid : " + toStringUUID);
819 return toStringUUID.equals(uuid);
820 } catch(IllegalArgumentException e) {
821 return false;
822 }
823 }
824 @Override
825 public void setResourceLoader(ResourceLoader resourceLoader) {
826 this.resourceLoader = resourceLoader;
827
828 }
829 }