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