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