1 // $Id: TaxonController.java 5473 2009-03-25 13:42:07Z a.kohlbecker $
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
11 package eu
.etaxonomy
.cdm
.remote
.controller
;
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
;
20 import java
.util
.NoSuchElementException
;
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
;
28 import javax
.servlet
.http
.HttpServletRequest
;
29 import javax
.servlet
.http
.HttpServletResponse
;
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
;
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
;
70 * @author a.kohlbecker
75 @RequestMapping(value
= {"/*/portal/taxon/*", "/*/portal/taxon/*/*", "/*/portal/name/*/*", "/*/portal/taxon/*/media/*/*"})
76 public class TaxonPortalController
extends BaseController
<TaxonBase
, ITaxonService
>
78 public static final Logger logger
= Logger
.getLogger(TaxonPortalController
.class);
81 private INameService nameService
;
83 private IDescriptionService descriptionService
;
85 private IReferenceService referenceService
;
88 private static final List
<String
> TAXON_INIT_STRATEGY
= Arrays
.asList(new String
[]{
91 "relationsToThisName.fromTaxon.name.taggedName",
95 "name.rank.representations",
96 "name.status.type.representations",
99 "descriptions.elements.$",
100 "descriptions.elements.area",
101 "descriptions.elements.area.$",
102 "descriptions.elements.multilanguageText",
103 "descriptions.elements.media.representations.parts",
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"
114 private static final List
<String
> SIMPLE_TAXON_INIT_STRATEGY
= Arrays
.asList(new String
[]{
117 "relationsToThisName.fromTaxon.name.taggedName",
121 "name.rank.representations",
122 "name.status.type.representations"
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",
138 "name.homotypicalGroup.$",
139 "name.homotypicalGroup.typifiedNames.$",
140 "name.homotypicalGroup.typifiedNames.name.taggedName",
142 "name.homotypicalGroup.typifiedNames.taxonBases.$",
143 "name.homotypicalGroup.typifiedNames.taxonBases.name.taggedName"
147 private static final List
<String
> TAXONRELATIONSHIP_INIT_STRATEGY
= Arrays
.asList(new String
[]{
149 "type.inverseRepresentations",
150 "fromTaxon.sec.authorTeam",
151 "fromTaxon.name.taggedName"
154 private static final List
<String
> NAMERELATIONSHIP_INIT_STRATEGY
= Arrays
.asList(new String
[]{
156 "type.inverseRepresentations",
157 "fromName.taggedName",
161 protected static final List
<String
> TAXONDESCRIPTION_INIT_STRATEGY
= Arrays
.asList(new String
[]{
164 "elements.citation.authorTeam",
165 "elements.multilanguageText",
166 "elements.media.representations.parts",
169 private static final List
<String
> NAMEDESCRIPTION_INIT_STRATEGY
= Arrays
.asList(new String
[]{
174 "elements.multilanguageText",
175 "elements.media.representations.parts",
178 private static final List
<String
> TYPEDESIGNATION_INIT_STRATEGY
= Arrays
.asList(new String
[]{
181 "typeStatus.representations",
182 "citation.authorTeam",
183 "typeName.taggedName"
188 private static final String featureTreeUuidPattern
= "^/(?:[^/]+)/taxon(?:(?:/)([^/?#&\\.]+))+.*";
190 public TaxonPortalController(){
192 setUuidParameterPattern("^/(?:[^/]+)/portal/(?:[^/]+)/([^/?#&\\.]+).*");
196 * @see eu.etaxonomy.cdm.remote.controller.GenericController#setService(eu.etaxonomy.cdm.api.service.IService)
200 public void setService(ITaxonService service
) {
201 this.service
= service
;
205 public void initBinder(WebDataBinder binder
) {
206 binder
.registerCustomEditor(UUID
.class, new UUIDPropertyEditor());
207 binder
.registerCustomEditor(NamedArea
.class, new NamedAreaPropertyEditor());
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);
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
)
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
) +")" );
239 if(page
== null){ page
= BaseListController
.DEFAULT_PAGE
;}
240 if(pageSize
== null){ pageSize
= BaseListController
.DEFAULT_PAGESIZE
;}
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
);
257 return (Pager
<IdentifiableEntity
>) service
.findTaxaAndNames(config
);
262 value
= {"/*/portal/taxon/*/synonymy"},
263 method
= RequestMethod
.GET
)
264 public ModelAndView
doGetSynonymy(HttpServletRequest request
, HttpServletResponse response
)throws IOException
{
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
);
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
{
283 logger
.info("getAccepted() " + request
.getServletPath());
285 UUID uuid
= readValueUuid(request
, null);
286 TaxonBase tb
= service
.load(uuid
, SYNONYMY_INIT_STRATEGY
);
288 response
.sendError(HttpServletResponse
.SC_NOT_FOUND
, "A taxon with the uuid " + uuid
+ " does not exist");
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
);
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
);
307 value
= {"/*/portal/taxon/*/taxonRelationships"},
308 method
= RequestMethod
.GET
)
309 public List
<TaxonRelationship
> doGetTaxonRelations(HttpServletRequest request
, HttpServletResponse response
)throws IOException
{
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
);
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
);
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();
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();
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();
365 * Usage /*/portal/name/{taxon
366 * uuid}/media/{mime type
367 * list}/{size}[,[widthOrDuration}][,{height}]/
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>
383 * @throws IOException
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
);
393 // pars the media and quality parameters
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
);
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;
418 for(int i
=0; i
<mimeTypes
.length
; i
++){
419 mimeTypes
[i
] = mimeTypes
[i
].replace(':', '/');
422 if(sizeTokens
.length
> 0){
424 size
= Integer
.valueOf(sizeTokens
[0]);
425 } catch (NumberFormatException nfe
) {
429 if(sizeTokens
.length
> 1){
431 widthOrDuration
= Integer
.valueOf(sizeTokens
[1]);
432 } catch (NumberFormatException nfe
) {
436 if(sizeTokens
.length
> 2){
438 height
= Integer
.valueOf(sizeTokens
[2]);
439 } catch (NumberFormatException nfe
) {
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
);
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
);
455 returnMedia
.add(media
);
456 } catch (NoSuchElementException nse
) {
466 * @param mimeTypeRegexes
468 * @param widthOrDuration
472 * TODO move into a media utils class
473 * TODO implement the quality filter
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()) {
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;
498 dwa = (representation.getParts().size() > 0 ? dwa / representation.getParts().size() : 0);
500 prefRepr
.put((dwa
+ representationCnt
++) + '_' + representation
.getMimeType(), representation
);
502 // preferred mime type found => end loop
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);
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();
523 // if(!featureTree.isDescriptionSeparated()){
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);
532 // List<TaxonDescription> separatedDescriptions = new ArrayList<TaxonDescription>(descriptions.size());
533 // separatedDescriptions.add(superDescription);
534 // return separatedDescriptions;
536 // return descriptions;
539 // response.sendError(HttpServletResponse.SC_NOT_FOUND, "invalid type; Taxon expected but " + tb.getClass().getSimpleName() + " found.");