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