Updated to remove calls to getRelatedFrom / getRelatedTo .
[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 NonViralName synnvn = (NonViralName) 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 NonViralName tonvn = (NonViralName) 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 NonViralName fromnvn = (NonViralName) 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 NonViralName nvn = (NonViralName) 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 //FIXME commenting out below line in order to allow fixing the bug #3064 (portal synonymy service produces massive response for Crepis tectorum)
611 Set<SynonymRelationship> synRelationships = synonym.getSynonymRelations();
612 for (SynonymRelationship sr : synRelationships) {
613 Taxon accTaxon = sr.getAcceptedTaxon();
614 String uuid = accTaxon.getUuid().toString();
615 logger.info("acc taxon uuid " + accTaxon.getUuid().toString() + " original hash code : " + System.identityHashCode(accTaxon) + ", name class " + accTaxon.getName().getClass().getName());
616 String title = accTaxon.getTitleCache();
617 logger.info("taxon title cache : " + accTaxon.getTitleCache());
618 //NonViralName accnvn = CdmBase.deproxy(accTaxon.getName(), NonViralName.class);
619 NonViralName accnvn = (NonViralName)accTaxon.getName();
620 String name = accnvn.getTitleCache();
621 String rank = accnvn.getRank().getTitleCache();
622 String status = ACCEPTED_NAME_STATUS;
623 String relLabel = sr.getType().getRepresentation(Language.DEFAULT())
624 .getLabel();
625 ti.addToResponseRelatedTaxa(uuid, title, name, rank, status, "", relLabel);
626 }
627
628 }
629 tiList.add(ti);
630 } else {
631 ErrorResponse re = new ErrorResponse();
632 if(isValid(taxonUuid)) {
633 re.setErrorMessage("No Taxon for given UUID : " + taxonUuid);
634 } else {
635 re.setErrorMessage(taxonUuid + " not a valid UUID");
636 }
637 tiList.add(re);
638 }
639 }
640 mv.addObject(tiList);
641 return mv;
642 }
643
644 /**
645 * Returns a list of all available classifications (with associated referenc information) and the default classification.
646 * <p>
647 * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-classification-info.html">here</a>
648 * <p>
649 * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/voc/classification</b>
650 *
651 * @param request Http servlet request.
652 * @param response Http servlet response.
653 * @return a List of {@link Classification} objects represebted as strings.
654 * These are initialized using the {@link #VOC_CLASSIFICATION_INIT_STRATEGY}
655 * @throws IOException
656 */
657 @RequestMapping(value = { "voc/classification" }, method = RequestMethod.GET, params = {})
658 public ModelAndView doGetClassificationMap(HttpServletRequest request, HttpServletResponse response) throws IOException {
659 List<Map> cmapList = new ArrayList<Map>();
660 Map<String, String> classifications = new HashMap<String, String>();
661 ModelAndView mv = new ModelAndView();
662 List<Classification> clist = getClassificationList(100);
663 boolean isFirst = true;
664 Iterator itr = clist.iterator();
665 // loop through all classifications and populate map with
666 // (classificationKey, reference) elements
667 while(itr.hasNext()) {
668 Classification c = (Classification) itr.next();
669 String refTitleCache = "";
670 String classificationKey = removeInternalWhitespace(c.getTitleCache());
671 if(c.getReference() != null) {
672 refTitleCache = c.getReference().getTitleCache();
673 }
674 // default is the first element of the list
675 // always created with the same sorting (DESCENDING)
676 if(isFirst) {
677 Map<String, String> defaultMap = new HashMap<String, String>();
678 defaultMap.put("default", classificationKey);
679 cmapList.add(defaultMap);
680 isFirst = false;
681 }
682 classifications.put(classificationKey, refTitleCache);
683
684 }
685 Map<String, Map> cmap = new HashMap<String, Map>();
686 cmap.put("classification",classifications);
687 cmapList.add(cmap);
688 mv.addObject(cmapList);
689 return mv;
690 }
691
692 /**
693 * Returns the match mode by parsing the input string of wildcards.
694 *
695 * @param query
696 * String to parse.
697 *
698 * @return {@link MatchMode} depending on the the position of the wildcard (*)
699 */
700 private MatchMode getMatchModeFromQuery(String query) {
701 if (query.startsWith("*") && query.endsWith("*")) {
702 return MatchMode.ANYWHERE;
703 } else if (query.startsWith("*")) {
704 return MatchMode.END;
705 } else if (query.endsWith("*")) {
706 return MatchMode.BEGINNING;
707 } else {
708 return MatchMode.EXACT;
709 }
710 }
711
712 /**
713 * Removes wildcards from the input string.
714 *
715 * @param query
716 * String to parse.
717 *
718 * @return input string with wildcards removed
719 */
720 private String getQueryWithoutWildCards(String query) {
721
722 String newQuery = query;
723
724 if (query.startsWith("*")) {
725 newQuery = newQuery.substring(1, newQuery.length());
726 }
727
728 if (query.endsWith("*")) {
729 newQuery = newQuery.substring(0, newQuery.length() - 1);
730 }
731
732 return newQuery.trim();
733 }
734
735 /**
736 * Build map with taxon flag key-value pairs.
737 */
738 private Map<String, String> buildFlagMap(TaxonBase tb) {
739 Map<String, String> flags = new Hashtable<String, String>();
740 flags.put(DOUBTFUL_FLAG, Boolean.toString(tb.isDoubtful()));
741 return flags;
742 }
743
744 /**
745 * Build classification map.
746 */
747 private Map<String, Map> getClassification(Taxon taxon, String classificationType) {
748 // Using TreeMap is important, because we need the sorting of the classification keys
749 // in the map to be stable.
750 TreeMap<String, Map> sourceClassificationMap = buildClassificationMap(taxon, classificationType);
751
752 // if classification key is 'default' then return the default element of the map
753 if(classificationType.equals(CLASSIFICATION_DEFAULT) && !sourceClassificationMap.isEmpty()) {
754 List<Classification> clist = getClassificationList(1);
755 String defaultKey = removeInternalWhitespace(clist.get(0).getTitleCache());
756 return sourceClassificationMap.get(defaultKey);
757 // if classification key is provided then return the classification corresponding to the key
758 } else if(sourceClassificationMap.containsKey(classificationType)) {
759 return sourceClassificationMap.get(classificationType);
760 // if classification key is 'all' then return the entire map
761 } else if(classificationType.equals(CLASSIFICATION_ALL)) {
762 return sourceClassificationMap;
763 } else {
764 return new TreeMap<String,Map>();
765 }
766 }
767
768 /**
769 * Build classification map.
770 */
771 private TreeMap<String, Map> buildClassificationMap(Taxon taxon, String classificationType) {
772 // Using TreeMap is important, because we need the sorting of the classification keys
773 // in the map to be stable.
774 TreeMap<String, Map> sourceClassificationMap = new TreeMap<String, Map>();
775 Set<TaxonNode> taxonNodes = taxon.getTaxonNodes();
776 //loop through taxon nodes and build classification map for each classification key
777 for (TaxonNode tn : taxonNodes) {
778 Map<String, String> classificationMap = new LinkedHashMap<String, String>();
779 List<TaxonNode> tnList = classificationService.loadTreeBranchToTaxon(taxon,
780 tn.getClassification(), null, TAXON_NODE_INIT_STRATEGY);
781 for (TaxonNode classificationtn : tnList) {
782 classificationMap.put(classificationtn.getTaxon().getName().getRank().getTitleCache(),
783 classificationtn.getTaxon().getName().getTitleCache());
784 }
785 String cname = removeInternalWhitespace(tn.getClassification().getTitleCache());
786 logger.info("Building classification map " + cname);
787 sourceClassificationMap.put(cname, classificationMap);
788 }
789 return sourceClassificationMap;
790 }
791
792 private String removeInternalWhitespace(String withWSpace) {
793 String[] words = withWSpace.split("\\s+");
794 // "\\s+" in regular expression language meaning one or
795 // more spaces
796 StringBuilder builder = new StringBuilder();
797 for (String word : words) {
798 builder.append(word);
799 }
800 return builder.toString();
801 }
802
803 private List<Classification> getClassificationList(int limit) {
804 List<OrderHint> orderHints = new ArrayList<OrderHint>();
805 orderHints.add(new OrderHint("titleCache", SortOrder.DESCENDING));
806 List<Classification> clist = classificationService.listClassifications(limit, 0, orderHints, VOC_CLASSIFICATION_INIT_STRATEGY);
807 return clist;
808 }
809
810 private boolean isValid(String uuid){
811 if( uuid == null) return false;
812 try {
813 // we have to convert to object and back to string because the built in fromString does not have
814 // good validation logic.
815
816 UUID fromStringUUID = UUID.fromString(uuid);
817 String toStringUUID = fromStringUUID.toString();
818
819 System.out.println("input uuid : " + uuid + " , parsed uuid : " + toStringUUID);
820 return toStringUUID.equals(uuid);
821 } catch(IllegalArgumentException e) {
822 return false;
823 }
824 }
825 @Override
826 public void setResourceLoader(ResourceLoader resourceLoader) {
827 this.resourceLoader = resourceLoader;
828
829 }
830 }