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