dataportal/webservice release v2.0
[cdmlib.git] / cdmlib-remote / src / main / java / eu / etaxonomy / cdm / remote / controller / TaxonPortalController.java
1 // $Id: TaxonController.java 5473 2009-03-25 13:42:07Z a.kohlbecker $
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.remote.controller;
12
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.NoSuchElementException;
21 import java.util.Set;
22 import java.util.SortedMap;
23 import java.util.TreeMap;
24 import java.util.UUID;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
30
31 import org.apache.commons.lang.ObjectUtils;
32 import org.apache.log4j.Logger;
33 import org.springframework.beans.factory.annotation.Autowired;
34 import org.springframework.stereotype.Controller;
35 import org.springframework.web.bind.WebDataBinder;
36 import org.springframework.web.bind.annotation.InitBinder;
37 import org.springframework.web.bind.annotation.RequestMapping;
38 import org.springframework.web.bind.annotation.RequestMethod;
39 import org.springframework.web.bind.annotation.RequestParam;
40 import org.springframework.web.servlet.ModelAndView;
41
42 import eu.etaxonomy.cdm.api.service.IDescriptionService;
43 import eu.etaxonomy.cdm.api.service.INameService;
44 import eu.etaxonomy.cdm.api.service.IReferenceService;
45 import eu.etaxonomy.cdm.api.service.ITaxonService;
46 import eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator;
47 import eu.etaxonomy.cdm.api.service.config.impl.TaxonServiceConfiguratorImpl;
48 import eu.etaxonomy.cdm.api.service.pager.Pager;
49 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
50 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
51 import eu.etaxonomy.cdm.model.description.TaxonDescription;
52 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
53 import eu.etaxonomy.cdm.model.location.NamedArea;
54 import eu.etaxonomy.cdm.model.media.Media;
55 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
56 import eu.etaxonomy.cdm.model.name.NameRelationship;
57 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
58 import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
59 import eu.etaxonomy.cdm.model.taxon.Synonym;
60 import eu.etaxonomy.cdm.model.taxon.Taxon;
61 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
62 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
63 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
64 import eu.etaxonomy.cdm.model.taxon.TaxonomicTree;
65 import eu.etaxonomy.cdm.persistence.query.MatchMode;
66 import eu.etaxonomy.cdm.remote.editor.NamedAreaPropertyEditor;
67 import eu.etaxonomy.cdm.remote.editor.UUIDPropertyEditor;
68
69 /**
70 * @author a.kohlbecker
71 *
72 */
73
74 @Controller
75 @RequestMapping(value = {"/*/portal/taxon/*", "/*/portal/taxon/*/*", "/*/portal/name/*/*", "/*/portal/taxon/*/media/*/*"})
76 public class TaxonPortalController extends BaseController<TaxonBase, ITaxonService>
77 {
78 public static final Logger logger = Logger.getLogger(TaxonPortalController.class);
79
80 @Autowired
81 private INameService nameService;
82 @Autowired
83 private IDescriptionService descriptionService;
84 @Autowired
85 private IReferenceService referenceService;
86
87
88 private static final List<String> TAXON_INIT_STRATEGY = Arrays.asList(new String []{
89 "*",
90 // taxon relations
91 "relationsToThisName.fromTaxon.name.taggedName",
92 // the name
93 "name.$",
94 "name.taggedName",
95 "name.rank.representations",
96 "name.status.type.representations",
97
98 // taxon descriptions
99 "descriptions.elements.$",
100 "descriptions.elements.area",
101 "descriptions.elements.area.$",
102 "descriptions.elements.multilanguageText",
103 "descriptions.elements.media.representations.parts",
104
105 // // typeDesignations
106 // "name.typeDesignations.$",
107 // "name.typeDesignations.citation.authorTeam",
108 // "name.typeDesignations.typeName.$",
109 // "name.typeDesignations.typeStatus.representations",
110 // "name.typeDesignations.typeSpecimen.media.representations.parts"
111
112 });
113
114 private static final List<String> SIMPLE_TAXON_INIT_STRATEGY = Arrays.asList(new String []{
115 "*",
116 // taxon relations
117 "relationsToThisName.fromTaxon.name.taggedName",
118 // the name
119 "name.$",
120 "name.taggedName",
121 "name.rank.representations",
122 "name.status.type.representations"
123 });
124
125 private static final List<String> SYNONYMY_INIT_STRATEGY = Arrays.asList(new String []{
126 // initialize homotypical and heterotypical groups; needs synonyms
127 "synonymRelations.$",
128 "synonymRelations.synonym.$",
129 "synonymRelations.synonym.name.taggedName",
130 "synonymRelations.synonym.name.nomenclaturalReference.inBook.authorTeam",
131 "synonymRelations.synonym.name.nomenclaturalReference.inJournal",
132 "synonymRelations.synonym.name.nomenclaturalReference.inProceedings",
133 "synonymRelations.synonym.name.homotypicalGroup.typifiedNames.$",
134 "synonymRelations.synonym.name.homotypicalGroup.typifiedNames.name.taggedName",
135 "synonymRelations.synonym.name.homotypicalGroup.typifiedNames.taxonBases.$",
136 "synonymRelations.synonym.name.homotypicalGroup.typifiedNames.taxonBases.name.taggedName",
137
138 "name.homotypicalGroup.$",
139 "name.homotypicalGroup.typifiedNames.$",
140 "name.homotypicalGroup.typifiedNames.name.taggedName",
141
142 "name.homotypicalGroup.typifiedNames.taxonBases.$",
143 "name.homotypicalGroup.typifiedNames.taxonBases.name.taggedName"
144
145 });
146
147 private static final List<String> TAXONRELATIONSHIP_INIT_STRATEGY = Arrays.asList(new String []{
148 "$",
149 "type.inverseRepresentations",
150 "fromTaxon.sec.authorTeam",
151 "fromTaxon.name.taggedName"
152 });
153
154 private static final List<String> NAMERELATIONSHIP_INIT_STRATEGY = Arrays.asList(new String []{
155 "$",
156 "type.inverseRepresentations",
157 "fromName.taggedName",
158 });
159
160
161 protected static final List<String> TAXONDESCRIPTION_INIT_STRATEGY = Arrays.asList(new String []{
162 "$",
163 "elements.$",
164 "elements.citation.authorTeam",
165 "elements.multilanguageText",
166 "elements.media.representations.parts",
167 });
168
169 private static final List<String> NAMEDESCRIPTION_INIT_STRATEGY = Arrays.asList(new String []{
170 "uuid",
171 "feature",
172
173 "elements.$",
174 "elements.multilanguageText",
175 "elements.media.representations.parts",
176 });
177
178 private static final List<String> TYPEDESIGNATION_INIT_STRATEGY = Arrays.asList(new String []{
179 //"$",
180 "typeSpecimen.$",
181 "typeStatus.representations",
182 "citation.authorTeam",
183 "typeName.taggedName"
184 });
185
186
187
188 private static final String featureTreeUuidPattern = "^/(?:[^/]+)/taxon(?:(?:/)([^/?#&\\.]+))+.*";
189
190 public TaxonPortalController(){
191 super();
192 setUuidParameterPattern("^/(?:[^/]+)/portal/(?:[^/]+)/([^/?#&\\.]+).*");
193 }
194
195 /* (non-Javadoc)
196 * @see eu.etaxonomy.cdm.remote.controller.GenericController#setService(eu.etaxonomy.cdm.api.service.IService)
197 */
198 @Autowired
199 @Override
200 public void setService(ITaxonService service) {
201 this.service = service;
202 }
203
204 @InitBinder
205 public void initBinder(WebDataBinder binder) {
206 binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());
207 binder.registerCustomEditor(NamedArea.class, new NamedAreaPropertyEditor());
208 }
209
210
211 @Override
212 @RequestMapping(method = RequestMethod.GET)
213 public TaxonBase doGet(HttpServletRequest request, HttpServletResponse response)throws IOException {
214 logger.info("doGet()");
215 TaxonBase tb = getCdmBase(request, response, TAXON_INIT_STRATEGY, TaxonBase.class);
216 return tb;
217 }
218
219 @RequestMapping(method = RequestMethod.GET,
220 value = {"/*/portal/taxon/find"}) //TODO map to path /*/portal/taxon/
221 //FIXME duplicate method see TaxonPortalListController.doFind() : TaxonPortalListController is disabled!
222 public Pager<IdentifiableEntity> doFind(
223 @RequestParam(value = "query", required = false) String query,
224 @RequestParam(value = "tree", required = false) UUID treeUuid,
225 @RequestParam(value = "area", required = false) Set<NamedArea> areas,
226 @RequestParam(value = "page", required = false) Integer page,
227 @RequestParam(value = "pageSize", required = false) Integer pageSize,
228 @RequestParam(value = "doTaxa", required = false) Boolean doTaxa,
229 @RequestParam(value = "doSynonyms", required = false) Boolean doSynonyms,
230 @RequestParam(value = "doTaxaByCommonNames", required = false) Boolean doTaxaByCommonNames)
231 throws IOException {
232
233 logger.info("doFind( " +
234 "query=\"" + ObjectUtils.toString(query) + "\", treeUuid=" + ObjectUtils.toString(treeUuid) +
235 ", area=" + ObjectUtils.toString(areas) +
236 ", pageSize=" + ObjectUtils.toString(pageSize) + ", page=" + ObjectUtils.toString(page) +
237 ", doTaxa=" + ObjectUtils.toString(doTaxa) + ", doSynonyms=" + ObjectUtils.toString(doSynonyms) +")" );
238
239 if(page == null){ page = BaseListController.DEFAULT_PAGE;}
240 if(pageSize == null){ pageSize = BaseListController.DEFAULT_PAGESIZE;}
241
242 ITaxonServiceConfigurator config = new TaxonServiceConfiguratorImpl();
243 config.setPageNumber(page);
244 config.setPageSize(pageSize);
245 config.setSearchString(query);
246 config.setDoTaxa(doTaxa!= null ? doTaxa : Boolean.FALSE );
247 config.setDoSynonyms(doSynonyms != null ? doSynonyms : Boolean.FALSE );
248 config.setDoTaxaByCommonNames(doTaxaByCommonNames != null ? doTaxaByCommonNames : Boolean.FALSE );
249 config.setMatchMode(MatchMode.BEGINNING);
250 config.setTaxonPropertyPath(SIMPLE_TAXON_INIT_STRATEGY);
251 config.setNamedAreas(areas);
252 if(treeUuid != null){
253 TaxonomicTree taxonomicTree = service.getTaxonomicTreeByUuid(treeUuid);
254 config.setTaxonomicTree(taxonomicTree);
255 }
256
257 return (Pager<IdentifiableEntity>) service.findTaxaAndNames(config);
258 }
259
260
261 @RequestMapping(
262 value = {"/*/portal/taxon/*/synonymy"},
263 method = RequestMethod.GET)
264 public ModelAndView doGetSynonymy(HttpServletRequest request, HttpServletResponse response)throws IOException {
265
266 logger.info("doGetSynonymy() " + request.getServletPath());
267 ModelAndView mv = new ModelAndView();
268 TaxonBase tb = getCdmBase(request, response, null, Taxon.class);
269 Taxon taxon = (Taxon)tb;
270 Map<String, List<?>> synonymy = new Hashtable<String, List<?>>();
271 synonymy.put("homotypicSynonymsByHomotypicGroup", service.getHomotypicSynonymsByHomotypicGroup(taxon, SYNONYMY_INIT_STRATEGY));
272 synonymy.put("heterotypicSynonymyGroups", service.getHeterotypicSynonymyGroups(taxon, SYNONYMY_INIT_STRATEGY));
273 mv.addObject(synonymy);
274 return mv;
275 }
276
277 @RequestMapping(value = "/*/portal/taxon/*/accepted", method = RequestMethod.GET)
278 public Set<TaxonBase> getAccepted(
279 @RequestParam(value = "page", required = false) Integer page,
280 @RequestParam(value = "pageSize", required = false) Integer pageSize,
281 HttpServletRequest request, HttpServletResponse response) throws IOException {
282
283 logger.info("getAccepted() " + request.getServletPath());
284
285 UUID uuid = readValueUuid(request, null);
286 TaxonBase tb = service.load(uuid, SYNONYMY_INIT_STRATEGY);
287 if(tb == null){
288 response.sendError(HttpServletResponse.SC_NOT_FOUND, "A taxon with the uuid " + uuid + " does not exist");
289 return null;
290 }
291 HashSet<TaxonBase> resultset = new HashSet<TaxonBase>();
292 if(tb instanceof Taxon){
293 //the taxon already is accepted
294 //FIXME take the current view into account once views are implemented!!!
295 resultset.add((Taxon)tb);
296 } else {
297 Synonym syn = (Synonym)tb;
298 for(TaxonBase accepted : syn.getAcceptedTaxa()){
299 accepted = service.load(accepted.getUuid(), SIMPLE_TAXON_INIT_STRATEGY);
300 resultset.add(accepted);
301 }
302 }
303 return resultset;
304 }
305
306 @RequestMapping(
307 value = {"/*/portal/taxon/*/taxonRelationships"},
308 method = RequestMethod.GET)
309 public List<TaxonRelationship> doGetTaxonRelations(HttpServletRequest request, HttpServletResponse response)throws IOException {
310
311 logger.info("doGetTaxonRelations()" + request.getServletPath());
312 TaxonBase tb = getCdmBase(request, response, null, Taxon.class);
313 Taxon taxon = (Taxon)tb;
314 List<TaxonRelationship> relations = new ArrayList<TaxonRelationship>();
315 List<TaxonRelationship> results = service.listToTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, null, TAXONRELATIONSHIP_INIT_STRATEGY);
316 relations.addAll(results);
317 results = service.listToTaxonRelationships(taxon, TaxonRelationshipType.INVALID_DESIGNATION_FOR(), null, null, null, TAXONRELATIONSHIP_INIT_STRATEGY);
318 relations.addAll(results);
319
320 return relations;
321 }
322
323 @RequestMapping(
324 value = {"/*/portal/taxon/*/nameRelationships"},
325 method = RequestMethod.GET)
326 public List<NameRelationship> doGetNameRelations(HttpServletRequest request, HttpServletResponse response)throws IOException {
327 logger.info("doGetNameRelations()" + request.getServletPath());
328 TaxonBase tb = getCdmBase(request, response, SIMPLE_TAXON_INIT_STRATEGY, Taxon.class);
329 List<NameRelationship> list = nameService.listToNameRelationships(tb.getName(), null, null, null, null, NAMERELATIONSHIP_INIT_STRATEGY);
330 return list;
331 }
332
333 @RequestMapping(
334 value = {"/*/portal/name/*/descriptions"},
335 method = RequestMethod.GET)
336 public List<TaxonNameDescription> doGetNameDescriptions(HttpServletRequest request, HttpServletResponse response)throws IOException {
337 logger.info("doGetNameDescriptions()" + request.getServletPath());
338 UUID nameUuuid = readValueUuid(request, null);
339 TaxonNameBase tnb = nameService.load(nameUuuid, null);
340 Pager<TaxonNameDescription> p = descriptionService.getTaxonNameDescriptions(tnb, null, null, NAMEDESCRIPTION_INIT_STRATEGY);
341 return p.getRecords();
342 }
343
344 @RequestMapping(
345 value = {"/*/portal/taxon/*/nameTypeDesignations"},
346 method = RequestMethod.GET)
347 public List<TypeDesignationBase> doGetNameTypeDesignations(HttpServletRequest request, HttpServletResponse response)throws IOException {
348 logger.info("doGetNameTypeDesignations()" + request.getServletPath());
349 TaxonBase tb = getCdmBase(request, response, SIMPLE_TAXON_INIT_STRATEGY, Taxon.class);
350 Pager<TypeDesignationBase> p = nameService.getTypeDesignations(tb.getName(), null, null, null, TYPEDESIGNATION_INIT_STRATEGY);
351 return p.getRecords();
352 }
353
354 @RequestMapping(
355 value = {"/*/portal/taxon/*/descriptions"},
356 method = RequestMethod.GET)
357 public List<TaxonDescription> doGetDescriptions(HttpServletRequest request, HttpServletResponse response)throws IOException {
358 logger.info("doGetDescriptions()" + request.getServletPath());
359 Taxon t = getCdmBase(request, response, null, Taxon.class);
360 Pager<TaxonDescription> p = descriptionService.getTaxonDescriptions(t, null, null, null, null, TAXONDESCRIPTION_INIT_STRATEGY);
361 return p.getRecords();
362 }
363
364 /**
365 * Usage &#x002F;*&#x002F;portal&#x002F;name&#x002F;{taxon
366 * uuid}&#x002F;media&#x002F;{mime type
367 * list}&#x002F;{size}[,[widthOrDuration}][,{height}]&#x002F;
368 *
369 * Whereas
370 * <ul>
371 * <li><b>{mime type list}</b>: a comma separated list of mime types, in the
372 * order of preference. The forward slashes contained in the mime types must
373 * be replaced by a colon. Regular expressions can be used. Each media
374 * associated with this given taxon is being searched whereas the first
375 * matching mime type matching a representation always rules.</li>
376 * <li><b>{size},{widthOrDuration},{height}</b>: <i>not jet implemented</i>
377 * valid values are an integer or the asterisk '*' as a wildcard</li>
378 * </ul>
379 *
380 * @param request
381 * @param response
382 * @return
383 * @throws IOException
384 */
385 @RequestMapping(
386 value = {"/*/portal/taxon/*/media/*/*"},
387 method = RequestMethod.GET)
388 public List<Media> doGetMedia(HttpServletRequest request, HttpServletResponse response)throws IOException {
389 logger.info("doGetMedia()" + request.getServletPath());
390 Taxon t = getCdmBase(request, response, null, Taxon.class);
391 Pager<TaxonDescription> p = descriptionService.getTaxonDescriptions(t, null, null, null, null, TAXONDESCRIPTION_INIT_STRATEGY);
392
393 // pars the media and quality parameters
394
395
396 // collect all media of the given taxon
397 boolean limitToGalleries = false;
398 List<Media> taxonMedia = new ArrayList<Media>();
399 for(TaxonDescription desc : p.getRecords()){
400 if(!limitToGalleries || desc.isImageGallery()){
401 for(DescriptionElementBase element : desc.getElements()){
402 for(Media media : element.getMedia()){
403 taxonMedia.add(media);
404 }
405 }
406 }
407 }
408
409 // find best matching representations of each media
410 String path = request.getServletPath();
411 String[] pathTokens = path.split("/");
412 String[] mimeTypes = pathTokens[6].split(",");
413 String[] sizeTokens = pathTokens[7].split(",");
414 Integer widthOrDuration = null;
415 Integer height = null;
416 Integer size = null;
417
418 for(int i=0; i<mimeTypes.length; i++){
419 mimeTypes[i] = mimeTypes[i].replace(':', '/');
420 }
421
422 if(sizeTokens.length > 0){
423 try {
424 size = Integer.valueOf(sizeTokens[0]);
425 } catch (NumberFormatException nfe) {
426 /* IGNORE */
427 }
428 }
429 if(sizeTokens.length > 1){
430 try {
431 widthOrDuration = Integer.valueOf(sizeTokens[1]);
432 } catch (NumberFormatException nfe) {
433 /* IGNORE */
434 }
435 }
436 if(sizeTokens.length > 2){
437 try {
438 height = Integer.valueOf(sizeTokens[2]);
439 } catch (NumberFormatException nfe) {
440 /* IGNORE */
441 }
442 }
443
444 List<Media> returnMedia = new ArrayList<Media>(taxonMedia.size());
445 for(Media media : taxonMedia){
446 SortedMap<String, MediaRepresentation> prefRepresentations = orderMediaRepresentations(media, mimeTypes, size, widthOrDuration, height);
447 try {
448 // take first one and remove all other representations
449 MediaRepresentation prefOne = prefRepresentations.get(prefRepresentations.firstKey());
450 for (MediaRepresentation representation : media.getRepresentations()) {
451 if (representation != prefOne) {
452 media.removeRepresentation(representation);
453 }
454 }
455 returnMedia.add(media);
456 } catch (NoSuchElementException nse) {
457 /* IGNORE */
458 }
459 }
460
461 return returnMedia;
462 }
463
464 /**
465 * @param media
466 * @param mimeTypeRegexes
467 * @param size
468 * @param widthOrDuration
469 * @param height
470 * @return
471 *
472 * TODO move into a media utils class
473 * TODO implement the quality filter
474 */
475 private SortedMap<String, MediaRepresentation> orderMediaRepresentations(Media media, String[] mimeTypeRegexes,
476 Integer size, Integer widthOrDuration, Integer height) {
477 SortedMap<String, MediaRepresentation> prefRepr = new TreeMap<String, MediaRepresentation>();
478 for (String mimeTypeRegex : mimeTypeRegexes) {
479 // getRepresentationByMimeType
480 Pattern mimeTypePattern = Pattern.compile(mimeTypeRegex);
481 int representationCnt = 0;
482 for (MediaRepresentation representation : media.getRepresentations()) {
483 Matcher mather = mimeTypePattern.matcher(representation.getMimeType());
484 if (mather.matches()) {
485 int dwa = 0;
486
487 /* TODO the quality filter part is being skipped
488 * // look for representation with the best matching parts
489 for (MediaRepresentationPart part : representation.getParts()) {
490 if (part instanceof ImageFile) {
491 ImageFile image = (ImageFile) part;
492 int dw = image.getWidth() * image.getHeight() - height * widthOrDuration;
493 if (dw < 0) {
494 dw *= -1;
495 }
496 dwa += dw;
497 }
498 dwa = (representation.getParts().size() > 0 ? dwa / representation.getParts().size() : 0);
499 }*/
500 prefRepr.put((dwa + representationCnt++) + '_' + representation.getMimeType(), representation);
501
502 // preferred mime type found => end loop
503 break;
504 }
505 }
506 }
507 return prefRepr;
508 }
509
510 // @RequestMapping(
511 // value = {"/*/portal/taxon/*/descriptions"},
512 // method = RequestMethod.GET)
513 // public List<TaxonDescription> doGetDescriptionsbyFeatureTree(HttpServletRequest request, HttpServletResponse response)throws IOException {
514 // TaxonBase tb = getCdmBase(request, response, null, Taxon.class);
515 // if(tb instanceof Taxon){
516 // //TODO this is a quick and dirty implementation -> generalize
517 // UUID featureTreeUuid = readValueUuid(request, featureTreeUuidPattern);
518 //
519 // FeatureTree featureTree = descriptionService.getFeatureTreeByUuid(featureTreeUuid);
520 // Pager<TaxonDescription> p = descriptionService.getTaxonDescriptions((Taxon)tb, null, null, null, null, TAXONDESCRIPTION_INIT_STRATEGY);
521 // List<TaxonDescription> descriptions = p.getRecords();
522 //
523 // if(!featureTree.isDescriptionSeparated()){
524 //
525 // TaxonDescription superDescription = TaxonDescription.NewInstance();
526 // //put all descriptionElements in superDescription and make it invisible
527 // for(TaxonDescription description: descriptions){
528 // for(DescriptionElementBase element: description.getElements()){
529 // superDescription.addElement(element);
530 // }
531 // }
532 // List<TaxonDescription> separatedDescriptions = new ArrayList<TaxonDescription>(descriptions.size());
533 // separatedDescriptions.add(superDescription);
534 // return separatedDescriptions;
535 // }else{
536 // return descriptions;
537 // }
538 // } else {
539 // response.sendError(HttpServletResponse.SC_NOT_FOUND, "invalid type; Taxon expected but " + tb.getClass().getSimpleName() + " found.");
540 // return null;
541 // }
542 // }
543
544 }