Project

General

Profile

Download (68.5 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2009 EDIT
3
* European Distributed Institute of Taxonomy 
4
* http://www.e-taxonomy.eu
5
* 
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.remote.controller.dto;
10

    
11
import java.io.IOException;
12
import java.io.InputStream;
13
import java.util.ArrayList;
14
import java.util.Arrays;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.Hashtable;
18
import java.util.Iterator;
19
import java.util.LinkedHashMap;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.Set;
23
import java.util.TreeMap;
24
import java.util.UUID;
25

    
26
import javax.servlet.http.HttpServletRequest;
27
import javax.servlet.http.HttpServletResponse;
28

    
29
import org.apache.lucene.document.Document;
30
import org.apache.lucene.queryParser.ParseException;
31
import org.joda.time.DateTime;
32
import org.joda.time.format.DateTimeFormat;
33
import org.joda.time.format.DateTimeFormatter;
34
import org.springframework.beans.factory.annotation.Autowired;
35
import org.springframework.context.ResourceLoaderAware;
36
import org.springframework.core.io.Resource;
37
import org.springframework.core.io.ResourceLoader;
38
import org.springframework.stereotype.Controller;
39
import org.springframework.web.bind.annotation.RequestMapping;
40
import org.springframework.web.bind.annotation.RequestMethod;
41
import org.springframework.web.bind.annotation.RequestParam;
42
import org.springframework.web.servlet.ModelAndView;
43

    
44
import eu.etaxonomy.cdm.api.service.IClassificationService;
45
import eu.etaxonomy.cdm.api.service.ICommonService;
46
import eu.etaxonomy.cdm.api.service.INameService;
47
import eu.etaxonomy.cdm.api.service.ITaxonService;
48
import eu.etaxonomy.cdm.api.service.search.DocumentSearchResult;
49
import eu.etaxonomy.cdm.common.DocUtils;
50
import eu.etaxonomy.cdm.hibernate.search.AcceptedTaxonBridge;
51
import eu.etaxonomy.cdm.model.common.CdmBase;
52
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
53
import eu.etaxonomy.cdm.model.common.Language;
54
import eu.etaxonomy.cdm.model.name.NonViralName;
55
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
56
import eu.etaxonomy.cdm.model.reference.Reference;
57
import eu.etaxonomy.cdm.model.taxon.Classification;
58
import eu.etaxonomy.cdm.model.taxon.Synonym;
59
import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
60
import eu.etaxonomy.cdm.model.taxon.Taxon;
61
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
62
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
63
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
64
import eu.etaxonomy.cdm.persistence.query.MatchMode;
65
import eu.etaxonomy.cdm.persistence.query.OrderHint;
66
import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
67
import eu.etaxonomy.cdm.remote.controller.BaseController;
68
import eu.etaxonomy.cdm.remote.dto.common.ErrorResponse;
69
import eu.etaxonomy.cdm.remote.dto.common.RemoteResponse;
70
import eu.etaxonomy.cdm.remote.dto.namecatalogue.AcceptedNameSearch;
71
import eu.etaxonomy.cdm.remote.dto.namecatalogue.NameInformation;
72
import eu.etaxonomy.cdm.remote.dto.namecatalogue.NameSearch;
73
import eu.etaxonomy.cdm.remote.dto.namecatalogue.TaxonInformation;
74
import eu.etaxonomy.cdm.remote.view.HtmlView;
75

    
76
/**
77
 * The controller class for the namespace 'name_catalogue'. This web service namespace
78
 * is an add-on to the already existing CDM REST API and provides information relating
79
 * to scientific names as well as taxa present in the underlying datasource.
80
 *
81
 * @author c.mathew
82
 * @version 1.1.0
83
 * @created 15-Apr-2012
84
 */
85

    
86
@Controller
87
@RequestMapping(value = { "/name_catalogue" })
88
public class NameCatalogueController extends BaseController<TaxonNameBase, INameService> implements ResourceLoaderAware {
89

    
90
    private ResourceLoader resourceLoader;
91

    
92
    /** Taxonomic status 'accepted' string */
93
    public static final String ACCEPTED_NAME_STATUS = "accepted";
94

    
95
    /** Taxonpmic status 'synonym' string */
96
    public static final String SYNONYM_STATUS = "synonym";
97

    
98
    /** Flag 'doubtful' strings */
99
    public static final String DOUBTFUL_FLAG = "doubtful";
100

    
101
    /** Base scientific name search type */
102
    public static final String NAME_SEARCH = "name";
103

    
104
    /** Complete scientific name search type */
105
    public static final String TITLE_SEARCH = "title";
106

    
107
    /** Default name search type */
108
    public static final String DEFAULT_SEARCH_TYPE = NAME_SEARCH;
109
    
110
    /** Default max number of hits for the exact name search  */
111
    public static final String DEFAULT_MAX_NB_FOR_EXACT_SEARCH = "100";
112

    
113
    /** Classifcation 'default' key */
114
    public static final String CLASSIFICATION_DEFAULT = "default";
115

    
116
    /** Classifcation 'all' key */
117
    public static final String CLASSIFICATION_ALL = "all";
118
    
119
    /** Classification to include uuids key */
120
    public static final String INCLUDE_CLUUIDS = "cluuids";    
121
    
122
    /** Fuzzy Name Cache search */
123
    public static final String FUZZY_NAME_CACHE = "name";
124
    
125
    /** Fuzzy Atomised Name search */
126
    public static final String FUZZY_ATOMISED = "atomised";
127
    
128
    private static final String DWC_DATASET_ID = "http://rs.tdwg.org/dwc/terms/datasetID";
129

    
130
    private static final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd-MM-yyyy");
131
    
132
    @Autowired
133
    private ITaxonService taxonService;
134
    
135
    
136
    @Autowired
137
    private IClassificationService classificationService;
138
    
139
    @Autowired
140
    private ICommonService commonService;
141
    
142
    /** Hibernate name search initialisation strategy */
143
    private static final List<String> NAME_SEARCH_INIT_STRATEGY = Arrays.asList(new String[] {
144
            "combinationAuthorTeam.$",
145
            "exCombinationAuthorTeam.$",
146
            "basionymAuthorTeam.$",
147
            "exBasionymAuthorTeam.$",
148
            "nameCache",
149
            "taxonBases",
150
            "taxonBases.synonymRelations.type.$"});
151
    
152
    /** Hibernate accepted name search initialisation strategy */
153
    private static final List<String> ACC_NAME_SEARCH_INIT_STRATEGY = Arrays.asList(new String[] {
154
            "nameCache",
155
            "taxonBases",
156
            "taxonBases.synonymRelations.acceptedTaxon.name.nameCache",
157
            "taxonBases.synonymRelations.acceptedTaxon.name.rank.titleCache",
158
            "taxonBases.synonymRelations.acceptedTaxon.taxonNodes.classification",
159
            "taxonBases.taxonNodes.classification",
160
            "taxonBases.relationsFromThisTaxon.type.$"});
161

    
162
    /** Hibernate name information initialisation strategy */
163
    private static final List<String> NAME_INFORMATION_INIT_STRATEGY = Arrays.asList(new String[] {
164
            "taxonBases",
165
            "status",
166
            "nomenclaturalReference.$",
167
            "combinationAuthorTeam.$",
168
            "exCombinationAuthorTeam.$",
169
            "basionymAuthorTeam.$",
170
            "exBasionymAuthorTeam.$",
171
            "relationsToThisName.fromName.$",
172
            "relationsToThisName.nomenclaturalReference.$",
173
            "relationsToThisName.type.$",
174
            "relationsFromThisName.toName.$",
175
            "relationsFromThisName.nomenclaturalReference.$",
176
            "relationsFromThisName.type.$"});
177

    
178
    /** Hibernate taxon information initialisation strategy */
179
    private static final List<String> TAXON_INFORMATION_INIT_STRATEGY = Arrays.asList(new String[] {
180
            "name.titleCache",
181
            "name.rank.titleCache",    
182
            
183
            "sec.updated",
184
            "sec.titleCache",
185
            "sources.citation.sources.idNamespace",
186
            "sources.citation.sources.idInSource",
187
            
188
            "synonymRelations.synonym.name.rank.titleCache",
189
            "synonymRelations.synonym.sec.updated",
190
            "synonymRelations.synonym.sec.titleCache",            
191
            "synonymRelations.synonym.sources.citation.sources.idNamespace",
192
            "synonymRelations.synonym.sources.citation.sources.idInSource",   
193
            "synonymRelations.acceptedTaxon.name.rank.titleCache",
194
            "synonymRelations.acceptedTaxon.sec.titleCache",
195
            "synonymRelations.acceptedTaxon.sources.citation.sources.idNamespace",
196
            "synonymRelations.acceptedTaxon.sources.citation.sources.idInSource",    
197
            "synonymRelations.type.inverseRepresentations",
198
            
199
            "relationsFromThisTaxon.type.inverseRepresentations",
200
            "relationsFromThisTaxon.toTaxon.name.rank.titleCache",
201
            "relationsFromThisTaxon.toTaxon.sec.updated",
202
            "relationsFromThisTaxon.toTaxon.sec.titleCache",
203
            "relationsFromThisTaxon.toTaxon.sources.citation.sources.idNamespace",
204
            "relationsFromThisTaxon.toTaxon.sources.citation.sources.idInSource",
205
            
206
            "relationsToThisTaxon.type.inverseRepresentations",
207
            "relationsToThisTaxon.fromTaxon.name.rank.titleCache",
208
            "relationsToThisTaxon.fromTaxon.sec.updated",
209
            "relationsToThisTaxon.fromTaxon.sec.titleCache",
210
            "relationsToThisTaxon.fromTaxon.sources.citation.sources.idNamespace",
211
            "relationsToThisTaxon.fromTaxon.sources.citation.sources.idInSource",
212
            
213
            "taxonNodes",
214
            "taxonNodes.classification" });
215

    
216
    /** Hibernate taxon node initialisation strategy */
217
    private static final List<String> TAXON_NODE_INIT_STRATEGY = Arrays.asList(new String[] {
218
            "taxon.sec",
219
            "taxon.name",
220
            "classification",
221
            "classification.reference.$",
222
            "classification.reference.authorTeam.$" });
223

    
224
    /** Hibernate classification vocabulary initialisation strategy */
225
    private static final List<String> VOC_CLASSIFICATION_INIT_STRATEGY = Arrays.asList(new String[] {
226
            "classification",
227
            "classification.reference.$",
228
            "classification.reference.authorTeam.$" });
229
    
230
    /** Hibernate classification vocabulary initialisation strategy */
231
    private static final List<String> COMMON_INIT_STRATEGY = Arrays.asList(new String[] {});
232
    
233
    public NameCatalogueController() {
234
        super();
235
        setInitializationStrategy(Arrays.asList(new String[] { "$" }));
236
    }
237

    
238
    /*
239
     * (non-Javadoc)
240
     *
241
     * @see
242
     * eu.etaxonomy.cdm.remote.controller.GenericController#setService(eu
243
     * .etaxonomy.cdm.api.service.IService)
244
     */
245
    @Autowired
246
    @Override
247
    public void setService(INameService service) {
248
        this.service = service;
249
    }
250

    
251
    /**
252
     * Returns a documentation page for the Name Search API.
253
     * <p>
254
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
255
     *
256
     * @param request
257
     * @param response
258
     * @return Html page describing the Name Search API
259
     * @throws IOException
260
     */
261
    @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {})
262
    public ModelAndView doGetNameSearchDocumentation(
263
            HttpServletRequest request, HttpServletResponse response)
264
            throws IOException {
265
        ModelAndView mv = new ModelAndView();
266
        // Read apt documentation file.
267
        Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-default.apt");
268
        // using input stream as this works for both files in the classes directory
269
        // as well as files inside jars
270
        InputStream aptInputStream = resource.getInputStream();
271
        // Build Html View
272
        Map<String, String> modelMap = new HashMap<String, String>();
273
        // Convert Apt to Html
274
        modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
275
        mv.addAllObjects(modelMap);
276

    
277
        HtmlView hv = new HtmlView();
278
        mv.setView(hv);
279
        return mv;
280
    }
281

    
282
    /**
283
     * Returns a list of scientific names matching the <code>{query}</code>
284
     * string pattern. Each of these scientific names is accompanied by a list of
285
     * name uuids, a list of taxon uuids, a list of accepted taxon uuids, etc.
286
     * <p>
287
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-default.html">here</a>
288
     * <p>
289
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
290
     *
291
     * @param queries
292
     *                The base scientific name pattern(s) to query for. The query can
293
     *                contain wildcard characters ('*'). The query can be
294
     *                performed with no wildcard or with the wildcard at the
295
     *                begin and / or end depending on the search pattern.
296
     * @param request Http servlet request.
297
     * @param response Http servlet response.
298
     * @return a list of {@link NameSearch} objects each corresponding to a
299
     *         single query. These are built from {@link TaxonNameBase}
300
     *         entities which are in turn initialized using
301
     *         the {@link #NAME_SEARCH_INIT_STRATEGY}
302
     * @throws IOException
303
     */
304
    @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {"query"})
305
    public ModelAndView doGetNameSearch(@RequestParam(value = "query", required = true) String[] queries,
306
            HttpServletRequest request, HttpServletResponse response) throws IOException {
307
    return doGetNameSearch(queries, DEFAULT_SEARCH_TYPE, DEFAULT_MAX_NB_FOR_EXACT_SEARCH, request, response);
308
    }
309

    
310
    /**
311
     * Returns a list of scientific names matching the <code>{query}</code>
312
     * string pattern. Each of these scientific names is accompanied by a list of
313
     * name uuids, a list of taxon uuids and a list of accepted taxon uuids.
314
     * <p>
315
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-default.html">here</a>
316
     * <p>
317
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
318
     *
319
     * @param query
320
     *             	The scientific name pattern(s) to query for. The query can
321
     *             	contain wildcard characters ('*'). The query can be
322
     *              performed with no wildcard or with the wildcard at the
323
     *              begin and / or end depending on the search pattern.
324
     * @param type
325
     *              The type of name to query. This be either
326
     *              "name" : scientific name corresponding to 'name cache' in CDM or
327
     *              "title" : complete name corresponding to 'title cache' in CDM
328
     * @param hits               
329
     *            	Maximum number of responses to be returned.    
330
     * @param request Http servlet request.
331
     * @param response Http servlet response.
332
     * @return a List of {@link NameSearch} objects each corresponding to a
333
     *         single query. These are built from {@link TaxonNameBase} entities
334
     *         which are in turn initialized using the {@link #NAME_SEARCH_INIT_STRATEGY}
335
     * @throws IOException
336
     */
337
    @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {"query", "type"})
338
    public ModelAndView doGetNameSearch(@RequestParam(value = "query", required = true) String[] queries,
339
            @RequestParam(value = "type", required = false, defaultValue = DEFAULT_SEARCH_TYPE) String searchType,
340
            @RequestParam(value = "hits", required = false, defaultValue = DEFAULT_MAX_NB_FOR_EXACT_SEARCH) String hits,
341
            HttpServletRequest request, HttpServletResponse response) throws IOException {
342
        ModelAndView mv = new ModelAndView();
343
        List<RemoteResponse> nsList = new ArrayList<RemoteResponse>();
344

    
345
        int h = 100;
346
        try {        	
347
        	h = Integer.parseInt(hits);
348
        } catch(NumberFormatException nfe) {
349
        	ErrorResponse er = new ErrorResponse();
350
        	er.setErrorMessage("hits parameter is not a number");
351
        	mv.addObject(er);
352
            return mv;
353
        }
354
        
355
        // search through each query
356
        for (String query : queries) {
357
        	if(query.equals("")) {
358
				ErrorResponse er = new ErrorResponse();
359
                er.setErrorMessage("Empty query field");
360
                nsList.add(er);
361
                continue;
362
        	}
363
        	// remove wildcards if any
364
            String queryWOWildcards = getQueryWithoutWildCards(query);
365
            // convert first char to upper case
366
            char[] stringArray = queryWOWildcards.toCharArray();
367
            stringArray[0] = Character.toUpperCase(stringArray[0]);
368
            queryWOWildcards = new String(stringArray);
369
            
370
            boolean wc = false;
371
            
372
            if(getMatchModeFromQuery(query) == MatchMode.BEGINNING) {
373
            	wc = true;
374
            }
375
            logger.info("doGetNameSearch()" + request.getRequestURI() + " for query \"" + query);
376
            
377
            List<DocumentSearchResult> nameSearchList = new ArrayList<DocumentSearchResult>();
378
            try {            	            
379
				nameSearchList = service.findByNameExactSearch(
380
				        queryWOWildcards,
381
				        wc,
382
				        null,
383
				        false, 
384
				        h);
385
			} catch (ParseException e) {
386
				// TODO Auto-generated catch block
387
				//e.printStackTrace();
388
				ErrorResponse er = new ErrorResponse();
389
                er.setErrorMessage("Could not parse name : " + query);
390
                nsList.add(er);
391
                continue;
392
			} 
393
     
394

    
395
            // if search is successful then get related information , else return error
396
            if (nameSearchList == null || !nameSearchList.isEmpty()) {
397
                NameSearch ns = new NameSearch();
398
                ns.setRequest(query);
399

    
400
                for (DocumentSearchResult searchResult : nameSearchList) {
401
                	for(Document doc : searchResult.getDocs()) {
402
                	
403
                    // we need to retrieve both taxon uuid of name queried and
404
                    // the corresponding accepted taxa.
405
                    // reason to return accepted taxa also, is to be able to get from
406
                    // scientific name to taxon concept in two web service calls.
407
                    List<String> tbUuidList = new ArrayList<String>();//nvn.getTaxonBases();
408
                    List<String> accTbUuidList = new ArrayList<String>();
409
                    String[] tbUuids = doc.getValues("taxonBases.uuid");
410
                    String[] tbClassNames = doc.getValues("taxonBases.classInfo.name");
411
                    for(int i=0;i<tbUuids.length;i++) {
412
                    	if(tbClassNames[i].equals("eu.etaxonomy.cdm.model.taxon.Taxon")) {
413
                    		accTbUuidList.add(tbUuids[i]);
414
                    	}
415
                    }
416
                    // update name search object
417
                    ns.addToResponseList(doc.getValues("titleCache")[0], 
418
                    		doc.getValues("nameCache")[0], 
419
                    		searchResult.getMaxScore(), 
420
                    		doc.getValues("uuid")[0].toString(), 
421
                    		doc.getValues("taxonBases.uuid"),
422
                    		mergeSynAccTaxonUuids(doc.getValues("taxonBases.accTaxon.uuids")));
423
                	}
424
                }
425
                nsList.add(ns);
426

    
427
            } else {
428
                ErrorResponse er = new ErrorResponse();
429
                er.setErrorMessage("No Taxon Name matches : " + query);
430
                nsList.add(er);
431
            }
432
        }        
433

    
434
        mv.addObject(nsList);
435
        return mv;
436
    }
437

    
438
    /**
439
     * Returns a documentation page for the Fuzzy Name Search API.
440
     * <p>
441
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/accepted</b>
442
     *
443
     * @param request Http servlet request.
444
     * @param response Http servlet response.
445
     * @return Html page describing the Fuzzy Name Search API
446
     * @throws IOException
447
     */
448
    @RequestMapping(value = { "fuzzy" }, method = RequestMethod.GET, params = {})
449
    public ModelAndView doGetNameFuzzySearchDocumentation(
450
            HttpServletRequest request, HttpServletResponse response)
451
            throws IOException {
452
        ModelAndView mv = new ModelAndView();
453
        // Read apt documentation file.
454
        Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-fuzzy.apt");
455
        // using input stream as this works for both files in the classes directory
456
        // as well as files inside jars
457
        InputStream aptInputStream = resource.getInputStream();
458
        // Build Html View
459
        Map<String, String> modelMap = new HashMap<String, String>();
460
        // Convert Apt to Html
461
        modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
462
        mv.addAllObjects(modelMap);
463

    
464
        HtmlView hv = new HtmlView();
465
        mv.setView(hv);
466
        return mv;
467
    }
468
    /**
469
     * Returns a list of scientific names similar to the <code>{query}</code>
470
     * string pattern. Each of these scientific names is accompanied by a list of
471
     * name uuids, a list of taxon uuids and a list of accepted taxon uuids.
472
     * The underlying (Lucene FuzzyQuery) string distance metric used is based on a 
473
     * fail-fast Levenshtein distance algorithm (is aborted if it is discovered that 
474
     * the mimimum distance between the words is greater than some threshold)
475
     * <p>
476
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-fuzzy.html">here</a>
477
     * <p>
478
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/fuzzy</b>
479
     *
480
     * @param query
481
     *                The scientific name pattern(s) to query for. Any wildcard characters in the
482
     *                query are removed.
483
     * @param accuracy
484
     *                Similarity measure (between 0 and 1) to impose on the matching algorithm.
485
     *                Briefly described, this is equivalent to the edit distance between the two words, divided by 
486
     *                the length of the shorter of the compared terms.
487
     * @param hits               
488
     *            Maximum number of responses to be returned.
489
     * @param type               
490
     *            The type of fuzzy search to call. This can be either
491
     *              "name" : fuzzy searches scientific names corresponding to 'name cache' in CDM or
492
     *              "atomised" : parses the query into atomised elements and fuzzy searches the individual elements in the CDM      
493
     * @param request Http servlet request.
494
     * @param response Http servlet response.
495
     * @return a List of {@link NameSearch} objects each corresponding to a
496
     *         single query. These are built from {@link TaxonNameBase} entities
497
     *         which are in turn initialized using the {@link #NAME_SEARCH_INIT_STRATEGY}
498
     * @throws IOException
499
     */
500
    @RequestMapping(value = { "fuzzy" }, method = RequestMethod.GET, params = {"query"})
501
    public ModelAndView doGetNameFuzzySearch(@RequestParam(value = "query", required = true) String[] queries,
502
    		@RequestParam(value = "accuracy", required = false, defaultValue = "0.6") String accuracy,
503
    		@RequestParam(value = "hits", required = false, defaultValue = "10") String hits,
504
    		@RequestParam(value = "type", required = false, defaultValue = FUZZY_NAME_CACHE) String type,
505
            HttpServletRequest request, HttpServletResponse response) throws IOException {
506
        ModelAndView mv = new ModelAndView();
507
        List<RemoteResponse> nsList = new ArrayList<RemoteResponse>();
508
        float acc = 0.5f;
509
        int h = 10;
510
        try {
511
        	acc = Float.parseFloat(accuracy);
512
        	h = Integer.parseInt(hits);
513
        } catch(NumberFormatException nfe) {
514
        	ErrorResponse er = new ErrorResponse();
515
        	er.setErrorMessage("accuracy or hits parameter is not a number");
516
        	mv.addObject(er);
517
            return mv;
518
        }
519
        
520
        if(acc < 0.0 || acc >= 1.0) {
521
        	ErrorResponse er = new ErrorResponse();
522
        	er.setErrorMessage("accuracy should be >= 0.0 and < 1.0");
523
        	mv.addObject(er);
524
            return mv;
525
        }
526
        // search through each query
527
        for (String query : queries) {
528
        	if(query.equals("")) {
529
				ErrorResponse er = new ErrorResponse();
530
                er.setErrorMessage("Empty query field");
531
                nsList.add(er);
532
                continue;
533
        	}
534
        	// remove wildcards if any
535
            String queryWOWildcards = getQueryWithoutWildCards(query);
536
            // convert first char to upper case
537
            char[] stringArray = queryWOWildcards.toCharArray();
538
            stringArray[0] = Character.toUpperCase(stringArray[0]);
539
            queryWOWildcards = new String(stringArray);
540
            logger.info("doGetNameSearch()" + request.getRequestURI() + " for query \"" + queryWOWildcards + " with accuracy " + accuracy);
541
            //List<NonViralName> nameList = new ArrayList<NonViralName>();
542
            List<DocumentSearchResult> nameSearchList = new ArrayList<DocumentSearchResult>();
543
            try {       
544
            	if(type.equals(FUZZY_ATOMISED)) {
545
            		nameSearchList = service.findByNameFuzzySearch(
546
            				queryWOWildcards,
547
            				acc,
548
            				null,
549
            				false, 
550
            				h);
551
            	} else {
552
            		nameSearchList = service.findByFuzzyNameCacheSearch(
553
            				queryWOWildcards,
554
            				acc,
555
            				null,
556
            				false, 
557
            				h);
558
            	}
559
            } catch (ParseException e) {
560
				// TODO Auto-generated catch block
561
				//e.printStackTrace();
562
				ErrorResponse er = new ErrorResponse();
563
                er.setErrorMessage("Could not parse name : " + queryWOWildcards);
564
                nsList.add(er);
565
                continue;
566
			} 
567
     
568

    
569
            // if search is successful then get related information , else return error
570
            if (nameSearchList == null || !nameSearchList.isEmpty()) {
571
                NameSearch ns = new NameSearch();
572
                ns.setRequest(query);
573

    
574
                for (DocumentSearchResult searchResult : nameSearchList) {
575
                	for(Document doc : searchResult.getDocs()) {
576
                	
577
                    // we need to retrieve both taxon uuid of name queried and
578
                    // the corresponding accepted taxa.
579
                    // reason to return accepted taxa also, is to be able to get from
580
                    // scientific name to taxon concept in two web service calls.
581
                    List<String> tbUuidList = new ArrayList<String>();//nvn.getTaxonBases();
582
                    List<String> accTbUuidList = new ArrayList<String>();
583
                    String[] tbUuids = doc.getValues("taxonBases.uuid");
584
                    String[] tbClassNames = doc.getValues("taxonBases.classInfo.name");
585
                    for(int i=0;i<tbUuids.length;i++) {
586
                    	if(tbClassNames[i].equals("eu.etaxonomy.cdm.model.taxon.Taxon")) {
587
                    		accTbUuidList.add(tbUuids[i]);
588
                    	}
589
                    }
590
                    // update name search object
591
                    ns.addToResponseList(doc.getValues("titleCache")[0], 
592
                    		doc.getValues("nameCache")[0], 
593
                    		searchResult.getMaxScore(), 
594
                    		doc.getValues("uuid")[0].toString(), 
595
                    		doc.getValues("taxonBases.uuid"),
596
                    		mergeSynAccTaxonUuids(doc.getValues("taxonBases.accTaxon.uuids")));
597
                	}
598
                }
599
                nsList.add(ns);
600

    
601
            } else {
602
                ErrorResponse er = new ErrorResponse();
603
                er.setErrorMessage("No Taxon Name matches : " + query + ", for given accuracy");
604
                nsList.add(er);
605
            }
606
        }        
607

    
608
        mv.addObject(nsList);
609
        return mv;
610
    }
611
    
612
    private String[] mergeSynAccTaxonUuids(String[] accTaxonUuids) {
613
    	List<String> accTaxonUuidList = new ArrayList<String>();
614
    	for(String accTaxonUuid : accTaxonUuids) {
615
    		for(String uuidListAsString : accTaxonUuid.split(AcceptedTaxonBridge.ACCEPTED_TAXON_UUID_LIST_SEP)) {
616
    			accTaxonUuidList.add(uuidListAsString);
617
    		}
618
    	}
619
    	return accTaxonUuidList.toArray(new String[0]);
620
    	
621
    }
622
    /**
623
     * Returns a documentation page for the Name Information API.
624
     * <p>
625
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/name</b>
626
     *
627
     * @param request Http servlet request.
628
     * @param response Http servlet response.
629
     * @return Html page describing the Name Information API
630
     * @throws IOException
631
     */
632
    @RequestMapping(value = { "name" }, method = RequestMethod.GET, params = {})
633
    public ModelAndView doGetNameInformationDocumentation(
634
            HttpServletRequest request, HttpServletResponse response)
635
            throws IOException {
636
        ModelAndView mv = new ModelAndView();
637
        // Read apt documentation file.
638
        Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-name-info.apt");
639
        // using input stream as this works for both files in the classes directory
640
        // as well as files inside jars
641
        InputStream aptInputStream = resource.getInputStream();
642
        // Build Html View
643
        Map<String, String> modelMap = new HashMap<String, String>();
644
        // Convert Apt to Html
645
        modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
646
        mv.addAllObjects(modelMap);
647

    
648
        HtmlView hv = new HtmlView();
649
        mv.setView(hv);
650
        return mv;
651
    }
652

    
653
    /**
654
     * Returns information related to the scientific name matching the given
655
     * <code>{nameUuid}</code>. The information includes the name string,
656
     * relationships, rank, list of related lsids / taxon uuids, etc.
657
     * <p>
658
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-name-info.html">here</a>
659
     * <p>
660
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/name</b>
661
     *
662
     * @param nameUuids uuid(s) of the scientific name to search for.
663
     * @param request Http servlet request.
664
     * @param response Http servlet response.
665
     * @return a List of {@link NameInformation} objects each corresponding to a
666
     *         single name uuid. These are built from {@link TaxonNameBase} entities
667
     *         which are in turn initialized using the {@link #NAME_INFORMATION_INIT_STRATEGY}
668
     * @throws IOException
669
     */
670
    @RequestMapping(value = { "name" }, method = RequestMethod.GET, params = {"nameUuid"})
671
    public ModelAndView doGetNameInformation(@RequestParam(value = "nameUuid", required = true) String[] nameUuids,
672
            HttpServletRequest request, HttpServletResponse response) throws IOException {
673
        ModelAndView mv = new ModelAndView();
674
        List<RemoteResponse> niList = new ArrayList<RemoteResponse>();
675
        // loop through each name uuid
676
        for (String nameUuid : nameUuids) {
677
            logger.info("doGetNameInformation()" + request.getRequestURI() + " for name uuid \""
678
                    + nameUuid + "\"");
679
            // find name by uuid
680
            NonViralName nvn = (NonViralName) service.findNameByUuid(UUID.fromString(nameUuid),
681
                        NAME_INFORMATION_INIT_STRATEGY);
682

    
683
            // if search is successful then get related information, else return error
684
            if (nvn != null) {
685
                NameInformation ni = new NameInformation();
686
                ni.setRequest(nameUuid);
687
                Reference ref = (Reference) nvn.getNomenclaturalReference();
688
                String citation = "";
689
                String citation_details = "";
690
                if (ref != null) {
691
                    citation = ref.getTitleCache();
692
                }
693
                // update name information object
694
                ni.setResponse(nvn.getTitleCache(), nvn.getNameCache(), nvn.getRank().getTitleCache(),
695
                        nvn.getStatus(), citation, nvn.getRelationsFromThisName(),
696
                        nvn.getRelationsToThisName(), nvn.getTaxonBases());
697
                niList.add(ni);
698
            } else {
699
                ErrorResponse re = new ErrorResponse();
700

    
701
                if(isValid(nameUuid)) {
702
                    re.setErrorMessage("No Name for given UUID : " + nameUuid);
703
                } else {
704
                    re.setErrorMessage(nameUuid + " not a valid UUID");
705
                }
706
                niList.add(re);
707
            }
708
        }
709
        mv.addObject(niList);
710
        return mv;
711
    }
712

    
713
    /**
714
     * Returns a documentation page for the Taxon Information API.
715
     * <p>
716
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/taxon</b>
717
     *
718
     * @param request Http servlet request.
719
     * @param response Http servlet response.
720
     * @return Html page describing the Taxon Information API
721
     * @throws IOException
722
     */
723
    @RequestMapping(value = { "taxon" }, method = RequestMethod.GET, params = {})
724
    public ModelAndView doGetTaxonInformation(
725
            HttpServletRequest request, HttpServletResponse response)
726
            throws IOException {
727
        ModelAndView mv = new ModelAndView();
728
        // Read apt documentation file.
729
        Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-taxon-info.apt");
730
        // using input stream as this works for both files in the classes directory
731
        // as well as files inside jars
732
        InputStream aptInputStream = resource.getInputStream();
733
        // Build Html View
734
        Map<String, String> modelMap = new HashMap<String, String>();
735
        // Convert Apt to Html
736
        modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
737
        mv.addAllObjects(modelMap);
738

    
739
        HtmlView hv = new HtmlView();
740
        mv.setView(hv);
741
        return mv;
742
    }
743

    
744
    /**
745
     * Returns information related to the taxon matching the given
746
     * <code>{taxonUuid}</code>. The information includes the name cache, title cache
747
     * relationship type, taxonomic status, information , etc.
748
     * <p>
749
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-taxon-info.html">here</a>
750
     * <p>
751
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
752
     *
753
     * @param taxonUuid
754
     *                 The taxon uuid to query for. The classification returned corresponds
755
     *                 to the first in the alphabetically sorted list of classifications
756
     *                 currently available in the database.
757
     *
758
     * @param request Http servlet request.
759
     * @param response Http servlet response.
760
     * @return a List of {@link TaxonInformation} objects each corresponding to a
761
     *         single query. These are built from {@TaxonBase} entities which are
762
     *         in turn initialized using the {@link #TAXON_INFORMATION_INIT_STRATEGY}
763
     * @throws IOException
764
     */
765
//    @RequestMapping(value = { "taxon" }, method = RequestMethod.GET,params = {"taxonUuid"})
766
//    public ModelAndView doGetTaxonInformation(
767
//            @RequestParam(value = "taxonUuid", required = true) String[] taxonUuids,
768
//            HttpServletRequest request, HttpServletResponse response) throws IOException {
769
//        return doGetTaxonInformation(taxonUuids,CLASSIFICATION_DEFAULT, new String[]{},request, response);
770
//    }
771

    
772
    /**
773
     * Returns information related to the taxon matching the given
774
     * <code>{taxonUuid}</code>. The information includes the name cache, title cache
775
     * relationship type, taxonomic status, information , etc.
776
     * <p>
777
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-taxon-info.html">here</a>
778
     * <p>
779
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/taxon</b>
780
     *
781
     * @param taxonUuid
782
     *                 The taxon uuid to query for.
783
     * @param classification
784
     *                 [Optional] String representing the taxonomic classification to use for
785
     *                 building the classification tree. Defaults to the first in the alphabetically
786
     *                 sorted list of classifications currently available in the database.
787
     * @param include
788
     *                 Array of data types to be included in addition to the normal response 
789
     *
790
     * @param request Http servlet request.
791
     * @param response Http servlet response.
792
     * @return a List of {@link TaxonInformation} objects each corresponding to a
793
     *         single query. These are built from {@TaxonBase} entities which are
794
     *         in turn initialized using the {@link #TAXON_INFORMATION_INIT_STRATEGY}
795
     * @throws IOException
796
     */
797
    @RequestMapping(value = { "taxon" }, method = RequestMethod.GET, params = {"taxonUuid"})
798
    public ModelAndView doGetTaxonInformation(
799
            @RequestParam(value = "taxonUuid", required = true) String[] taxonUuids,
800
            @RequestParam(value = "classification", required = false, defaultValue = CLASSIFICATION_DEFAULT) String classificationType,
801
            @RequestParam(value = "include", required = false, defaultValue = "") String[] includes,
802
            HttpServletRequest request, HttpServletResponse response) throws IOException {
803
        ModelAndView mv = new ModelAndView();
804
        List<RemoteResponse> tiList = new ArrayList<RemoteResponse>();
805
        // loop through each name uuid
806
        for (String taxonUuid : taxonUuids) {
807
            logger.info("doGetTaxonInformation()" + request.getRequestURI() + " for taxon uuid \""
808
                    + taxonUuid);
809
            // find name by uuid
810
            TaxonBase tb = taxonService.findTaxonByUuid(UUID.fromString(taxonUuid),
811
                    TAXON_INFORMATION_INIT_STRATEGY);
812
            
813
            // if search is successful then get related information, else return error
814
            if (tb != null) {
815
                TaxonInformation ti = new TaxonInformation();
816
                ti.setRequest(taxonUuid);
817
                // check if result (taxon base) is a taxon or synonym
818
                if (tb.isInstanceOf(Taxon.class)) {
819
                    Taxon taxon = (Taxon) tb;
820
                    // build classification map
821
                    boolean includeUuids = Arrays.asList(includes).contains(INCLUDE_CLUUIDS);
822
                    Map classificationMap = getClassification(taxon, classificationType, includeUuids);
823

    
824
                    logger.info("taxon uuid " + taxon.getUuid().toString() + " original hash code : " + System.identityHashCode(taxon) + ", name class " + taxon.getName().getClass().getName());
825
                    // update taxon information object with taxon related data
826
                    NonViralName nvn = CdmBase.deproxy(taxon.getName(),NonViralName.class);
827

    
828
                    String secTitle = "" ;
829
                    String modified = "";
830
                    if(taxon.getSec() != null) {
831
                    	secTitle = taxon.getSec().getTitleCache();
832
                    	DateTime dt = taxon.getUpdated();                               
833
                        modified = fmt.print(dt);
834
                    }
835
                    
836
                    Set<IdentifiableSource> sources = taxon.getSources();
837
                    String[] didname = getDatasetIdName(sources);                    
838

    
839
                    ti.setResponseTaxon(tb.getTitleCache(),
840
                            nvn.getTitleCache(),
841
                            nvn.getRank().getTitleCache(),
842
                            ACCEPTED_NAME_STATUS,
843
                            buildFlagMap(tb),
844
                            classificationMap,
845
                            "",
846
                            didname[0],
847
                            didname[1],
848
                            secTitle,
849
                            modified);
850
                    
851
                    
852
                    Set<SynonymRelationship> synRelationships = taxon.getSynonymRelations();
853
                    // add synonyms (if exists) to taxon information object
854
                    for (SynonymRelationship sr : synRelationships) {
855
                        Synonym syn = sr.getSynonym();
856
                        String uuid = syn.getUuid().toString();
857
                        String title = syn.getTitleCache();
858
                        TaxonNameBase synnvn = (TaxonNameBase) syn.getName();
859
                        String name = synnvn.getTitleCache();
860
                        String rank = (synnvn.getRank() == null)? "" : synnvn.getRank().getTitleCache();
861
                        String status = SYNONYM_STATUS;
862
                        String relLabel = sr.getType()
863
                                .getInverseRepresentation(Language.DEFAULT())
864
                                .getLabel();
865
                        
866
                        secTitle = "" ;
867
                        modified = "";
868
                        if(syn.getSec() != null) {
869
                        	secTitle = syn.getSec().getTitleCache();
870
                        	DateTime dt = syn.getUpdated();                               
871
                            modified = fmt.print(dt);
872
                        }
873
                        
874
                        sources = syn.getSources();
875
                        didname = getDatasetIdName(sources);
876
                                                
877
                        ti.addToResponseRelatedTaxa(uuid, 
878
                        		title, 
879
                        		name, 
880
                        		rank, 
881
                        		status, 
882
                        		relLabel,
883
                        		"",
884
                                didname[0],
885
                                didname[1],
886
                        		secTitle,
887
                                modified);
888
                    }
889

    
890
                    // build relationship information as,
891
                    // - relationships from the requested taxon
892
                    Set<TaxonRelationship> trFromSet = taxon.getRelationsFromThisTaxon();
893
                    for (TaxonRelationship tr : trFromSet) {                        
894
                        String titleTo = tr.getToTaxon().getTitleCache();
895
                        TaxonNameBase tonvn = (TaxonNameBase) tr.getToTaxon().getName();
896
                        String name = tonvn.getTitleCache();
897
                        String rank = tonvn.getRank().getTitleCache();
898
                        String uuid = tr.getToTaxon().getUuid().toString();
899
                        String status = ACCEPTED_NAME_STATUS;
900
                        String relLabel = tr.getType().getRepresentation(Language.DEFAULT())
901
                                .getLabel();
902

    
903
                        secTitle = "" ;
904
                        modified = "";
905
                        if(tr.getToTaxon().getSec() != null) {
906
                        	secTitle = tr.getToTaxon().getSec().getTitleCache();
907
                        	DateTime dt = tr.getToTaxon().getUpdated();                               
908
                            modified = fmt.print(dt);
909
                        }
910
                        
911
                        sources = tr.getToTaxon().getSources();
912
                        didname = getDatasetIdName(sources);
913
                                                
914
                        ti.addToResponseRelatedTaxa(uuid, 
915
                        		titleTo, 
916
                        		name, 
917
                        		rank, 
918
                        		status, 
919
                        		relLabel,
920
                        		"",
921
                                didname[0],
922
                                didname[1],
923
                        		secTitle,
924
                                modified);
925
                        //logger.info("titleTo : " + titleTo + " , name : " + name);
926
                    }
927

    
928
                    // - relationships to the requested taxon
929
                    Set<TaxonRelationship> trToSet = taxon.getRelationsToThisTaxon();
930
                    for (TaxonRelationship tr : trToSet) {
931
                        String titleFrom = tr.getFromTaxon().getTitleCache();
932
                        TaxonNameBase fromnvn = (TaxonNameBase) tr.getFromTaxon().getName();
933
                        String name = fromnvn.getTitleCache();
934
                        String rank = fromnvn.getRank().getTitleCache();
935
                        String uuid = tr.getFromTaxon().getUuid().toString();
936
                        String status = ACCEPTED_NAME_STATUS;
937
                        String relLabel = tr.getType()
938
                                .getInverseRepresentation(Language.DEFAULT())
939
                                .getLabel();
940

    
941
                        if(tr.getFromTaxon().getSec() != null) {
942
                        	secTitle = tr.getFromTaxon().getSec().getTitleCache();
943
                        	DateTime dt = tr.getFromTaxon().getSec().getUpdated();                               
944
                            modified = fmt.print(dt);
945
                        }
946
                        
947
                        sources = tr.getFromTaxon().getSources();
948
                        didname = getDatasetIdName(sources);
949
                        
950
                        secTitle = (tr.getFromTaxon().getSec() == null) ? "" : tr.getFromTaxon().getSec().getTitleCache();
951
                        ti.addToResponseRelatedTaxa(uuid, 
952
                        		titleFrom, 
953
                        		name, 
954
                        		rank, 
955
                        		status,
956
                        		relLabel,
957
                        		"",
958
                                didname[0],
959
                                didname[1],
960
                        		secTitle,
961
                                modified);
962
                        //logger.info("titleFrom : " + titleFrom + " , name : " + name);
963
                    }
964
                } else if (tb instanceof Synonym) {
965
                    Synonym synonym = (Synonym) tb;
966
                    TaxonNameBase nvn = (TaxonNameBase) synonym.getName();
967
                 // update taxon information object with synonym related data
968
                    DateTime dt = synonym.getUpdated();                    
969
                    String modified = fmt.print(dt);
970
                    
971
                    Set<IdentifiableSource> sources = synonym.getSources();
972
                    String[] didname = getDatasetIdName(sources);
973
                    
974
                    String secTitle = (synonym.getSec() == null) ? "" : synonym.getSec().getTitleCache();
975
                    ti.setResponseTaxon(synonym.getTitleCache(),
976
                            nvn.getTitleCache(),
977
                            nvn.getRank().getTitleCache(),
978
                            SYNONYM_STATUS,
979
                            buildFlagMap(synonym),
980
                            new TreeMap<String,Map>(),
981
                            "",
982
                            didname[0],
983
                            didname[1],
984
                    		secTitle,
985
                            modified);
986
                    // add accepted taxa (if exists) to taxon information object
987
                    
988
                    Set<SynonymRelationship> synRelationships = synonym.getSynonymRelations();
989
                    for (SynonymRelationship sr : synRelationships) {
990
                        Taxon accTaxon = sr.getAcceptedTaxon();                        
991
                        String uuid = accTaxon.getUuid().toString();
992
                        logger.info("acc taxon uuid " + accTaxon.getUuid().toString() + " original hash code : " + System.identityHashCode(accTaxon) + ", name class " + accTaxon.getName().getClass().getName());
993
                        String title = accTaxon.getTitleCache();
994
                        logger.info("taxon title cache : " + accTaxon.getTitleCache());
995
                                           
996
                        TaxonNameBase accnvn = (TaxonNameBase)accTaxon.getName();
997
                        String name = accnvn.getTitleCache();
998
                        String rank = accnvn.getRank().getTitleCache();
999
                        String status = ACCEPTED_NAME_STATUS;
1000
                        String relLabel = sr.getType().getRepresentation(Language.DEFAULT())
1001
                                .getLabel();
1002
                        dt = accTaxon.getUpdated();                    
1003
                        modified = fmt.print(dt);
1004
                        
1005
                        sources = accTaxon.getSources();
1006
                        didname = getDatasetIdName(sources);
1007
                        
1008
                        secTitle = (accTaxon.getSec() == null) ? "" : accTaxon.getSec().getTitleCache();
1009
                        ti.addToResponseRelatedTaxa(uuid, 
1010
                        		title, 
1011
                        		name, 
1012
                        		rank, 
1013
                        		status, 
1014
                        		relLabel,
1015
                        		"",
1016
                                didname[0],
1017
                                didname[1],
1018
                        		secTitle,
1019
                                modified);
1020
                    }
1021

    
1022
                }
1023
                tiList.add(ti);
1024
            } else {
1025
                ErrorResponse re = new ErrorResponse();
1026
                if(isValid(taxonUuid)) {
1027
                    re.setErrorMessage("No Taxon for given UUID : " + taxonUuid);
1028
                } else {
1029
                    re.setErrorMessage(taxonUuid + " not a valid UUID");
1030
                }
1031
                tiList.add(re);
1032
            }
1033
        }
1034
        mv.addObject(tiList);
1035
        return mv;
1036
    }
1037
    
1038
    /**
1039
     * Returns a documentation page for the Accepted Name Search API.
1040
     * <p>
1041
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/accepted</b>
1042
     *
1043
     * @param request Http servlet request.
1044
     * @param response Http servlet response.
1045
     * @return Html page describing the Accepted Name Search API
1046
     * @throws IOException
1047
     */
1048
    @RequestMapping(value = { "accepted" }, method = RequestMethod.GET, params = {})
1049
    public ModelAndView doGetAcceptedNameSearchDocumentation(
1050
            HttpServletRequest request, HttpServletResponse response)
1051
            throws IOException {
1052
        ModelAndView mv = new ModelAndView();
1053
        // Read apt documentation file.
1054
        Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-accepted.apt");
1055
        // using input stream as this works for both files in the classes directory
1056
        // as well as files inside jars
1057
        InputStream aptInputStream = resource.getInputStream();
1058
        // Build Html View
1059
        Map<String, String> modelMap = new HashMap<String, String>();
1060
        // Convert Apt to Html
1061
        modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
1062
        mv.addAllObjects(modelMap);
1063

    
1064
        HtmlView hv = new HtmlView();
1065
        mv.setView(hv);
1066
        return mv;
1067
    }
1068
    
1069
    /**
1070
     * Returns a list of accepted names of input scientific names matching the <code>{query}</code>
1071
     * string pattern. Each of these scientific names is accompanied by a list of
1072
     * name uuids, a list of taxon uuids and a list of accepted taxon uuids.
1073
     * <p>
1074
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-default.html">here</a>
1075
     * <p>
1076
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
1077
     *
1078
     * @param query
1079
     *                The scientific name pattern(s) to query for. The query can
1080
     *                contain wildcard characters ('*'). The query can be
1081
     *                performed with no wildcard or with the wildcard at the
1082
     *                begin and / or end depending on the search pattern.
1083
     * @param type
1084
     *                The type of name to query. This be either
1085
     *                "name" : scientific name corresponding to 'name cache' in CDM or
1086
     *                "title" : complete name corresponding to 'title cache' in CDM
1087
     * @param request Http servlet request.
1088
     * @param response Http servlet response.
1089
     * @return a List of {@link NameSearch} objects each corresponding to a
1090
     *         single query. These are built from {@link TaxonNameBase} entities
1091
     *         which are in turn initialized using the {@link #NAME_SEARCH_INIT_STRATEGY}
1092
     * @throws IOException
1093
     */
1094
    @RequestMapping(value = { "accepted" }, method = RequestMethod.GET, params = {"query"})
1095
    public ModelAndView doGetAcceptedNameSearch(@RequestParam(value = "query", required = true) String[] queries,
1096
    		HttpServletRequest request, HttpServletResponse response) throws IOException {
1097
    	return doGetAcceptedNameSearch(queries, DEFAULT_SEARCH_TYPE, request, response);
1098
    }
1099
    /**
1100
     * Returns a list of accepted names of input scientific names matching the <code>{query}</code>
1101
     * string pattern. Each of these scientific names is accompanied by a list of
1102
     * name uuids, a list of taxon uuids and a list of accepted taxon uuids.
1103
     * <p>
1104
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-default.html">here</a>
1105
     * <p>
1106
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue</b>
1107
     *
1108
     * @param query
1109
     *                The scientific name pattern(s) to query for. The query can
1110
     *                contain wildcard characters ('*'). The query can be
1111
     *                performed with no wildcard or with the wildcard at the
1112
     *                begin and / or end depending on the search pattern.
1113
     * @param type
1114
     *                The type of name to query. This be either
1115
     *                "name" : scientific name corresponding to 'name cache' in CDM or
1116
     *                "title" : complete name corresponding to 'title cache' in CDM
1117
     * @param request Http servlet request.
1118
     * @param response Http servlet response.
1119
     * @return a List of {@link NameSearch} objects each corresponding to a
1120
     *         single query. These are built from {@link TaxonNameBase} entities
1121
     *         which are in turn initialized using the {@link #NAME_SEARCH_INIT_STRATEGY}
1122
     * @throws IOException
1123
     */
1124
    @RequestMapping(value = { "accepted" }, method = RequestMethod.GET, params = {"query", "type"})
1125
    public ModelAndView doGetAcceptedNameSearch(@RequestParam(value = "query", required = true) String[] queries,
1126
            @RequestParam(value = "type", required = false, defaultValue = DEFAULT_SEARCH_TYPE) String searchType,
1127
            HttpServletRequest request, HttpServletResponse response) throws IOException {
1128
        ModelAndView mv = new ModelAndView();
1129
        List<RemoteResponse> ansList = new ArrayList<RemoteResponse>();
1130
        logger.info("doGetAcceptedNameSearch()");
1131
        
1132
        // if search type is not known then return error
1133
        if (!searchType.equals(NAME_SEARCH) && !searchType.equals(TITLE_SEARCH)) {
1134
            ErrorResponse er = new ErrorResponse();
1135
            er.setErrorMessage("searchType parameter can only be set as" + NAME_SEARCH + " or "
1136
                    + TITLE_SEARCH);
1137
            mv.addObject(er);
1138
            return mv;
1139
        }
1140

    
1141
        // search through each query
1142
        for (String query : queries) {
1143

    
1144
            //String queryWOWildcards = getQueryWithoutWildCards(query);
1145
            //MatchMode mm = getMatchModeFromQuery(query);
1146
            logger.info("doGetAcceptedNameSearch()" + request.getRequestURI() + " for query \"" + query);
1147
            List<NonViralName> nameList = new ArrayList<NonViralName>();
1148

    
1149
            // if "name" search then find by name cache
1150
            if (searchType.equals(NAME_SEARCH)) {
1151
                nameList = (List<NonViralName>) service.findNamesByNameCache(query, MatchMode.EXACT,
1152
                        ACC_NAME_SEARCH_INIT_STRATEGY);
1153
            }
1154

    
1155
            //if "title" search then find by title cache
1156
            if (searchType.equals(TITLE_SEARCH)) {
1157
                nameList = (List<NonViralName>) service.findNamesByTitleCache(query, MatchMode.EXACT,
1158
                        ACC_NAME_SEARCH_INIT_STRATEGY);
1159
            }
1160

    
1161
            // if search is successful then get related information , else return error
1162
            if (nameList == null || !nameList.isEmpty()) {
1163
                AcceptedNameSearch ans = new AcceptedNameSearch();
1164
                ans.setRequest(query);
1165
                
1166
                for (NonViralName nvn : nameList) {
1167
                    // we need to retrieve both taxon uuid of name queried and
1168
                    // the corresponding accepted taxa.
1169
                    // reason to return accepted taxa also, is to be able to get from
1170
                    // scientific name to taxon concept in two web service calls.
1171
                    Set<TaxonBase> tbSet = nvn.getTaxonBases();
1172
                    Set<TaxonBase> accTbSet = new HashSet<TaxonBase>();
1173
                    for (TaxonBase tb : tbSet) {
1174
                        // if synonym then get accepted taxa.
1175
                        if (tb instanceof Synonym) {
1176
                            Synonym synonym = (Synonym) tb;
1177
                            Set<SynonymRelationship> synRelationships = synonym.getSynonymRelations();
1178
                            for (SynonymRelationship sr : synRelationships) {
1179
                                Taxon accTaxon = sr.getAcceptedTaxon();
1180
                                NonViralName accNvn = CdmBase.deproxy(accTaxon.getName(),NonViralName.class);
1181
                                Map classificationMap = getClassification(accTaxon, CLASSIFICATION_DEFAULT, false);
1182
                                ans.addToResponseList(accNvn.getNameCache(),accNvn.getAuthorshipCache(), accNvn.getRank().getTitleCache(),classificationMap);
1183
                            }
1184
                        } else {
1185
                        	Taxon taxon = (Taxon)tb;
1186
                        	Set<TaxonRelationship> trFromSet = taxon.getRelationsFromThisTaxon();
1187
                        	boolean isConceptRelationship = true;
1188
                        	if(trFromSet.size() == 1) {
1189
                        		for (TaxonRelationship tr : trFromSet) {  
1190
                        			if(!tr.getType().isConceptRelationship()) {
1191
                        				// this is not a concept relationship, so it does not have an
1192
                        				// accepted name
1193
                        				isConceptRelationship = false;
1194

    
1195
                        			}
1196
                        		}
1197
                        	}
1198
                            if(isConceptRelationship) {
1199
                            	Map classificationMap = getClassification(taxon, CLASSIFICATION_DEFAULT, false);
1200
                            	ans.addToResponseList(nvn.getNameCache(), nvn.getAuthorshipCache(),nvn.getRank().getTitleCache(), classificationMap);
1201
                            }
1202
                        	
1203
                        }
1204
                    }
1205
                    // update name search object
1206
                    
1207
                }
1208
                ansList.add(ans);
1209

    
1210
            } else {
1211
                ErrorResponse er = new ErrorResponse();
1212
                er.setErrorMessage("No Taxon Name for given query : " + query);
1213
                ansList.add(er);
1214
            }
1215
        }        
1216

    
1217
        mv.addObject(ansList);
1218
        return mv;
1219
    }
1220

    
1221
    /**
1222
     * Returns a list of all available classifications (with associated referenc information) and the default classification.
1223
     * <p>
1224
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-classification-info.html">here</a>
1225
     * <p>
1226
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/voc/classification</b>
1227
     *
1228
     * @param request Http servlet request.
1229
     * @param response Http servlet response.
1230
     * @return a List of {@link Classification} objects represebted as strings.
1231
     *         These are initialized using the {@link #VOC_CLASSIFICATION_INIT_STRATEGY}
1232
     * @throws IOException
1233
     */
1234
    @RequestMapping(value = { "voc/classification" }, method = RequestMethod.GET, params = {})
1235
    public ModelAndView doGetClassificationMap(HttpServletRequest request, HttpServletResponse response) throws IOException {
1236
        List<Map> cmapList = new ArrayList<Map>();
1237
        Map<String, String> classifications = new HashMap<String, String>();
1238
        ModelAndView mv = new ModelAndView();
1239
        List<Classification> clist = getClassificationList(100);
1240
        boolean isFirst = true;
1241
        Iterator itr = clist.iterator();
1242
        // loop through all classifications and populate map with
1243
        // (classificationKey, reference) elements
1244
        while(itr.hasNext()) {
1245
            Classification c = (Classification) itr.next();
1246
            String refTitleCache = "";
1247
            String classificationKey = removeInternalWhitespace(c.getTitleCache());
1248
            if(c.getReference() != null) {
1249
                refTitleCache = c.getReference().getTitleCache();
1250
                c.getAllNodes();
1251
            }
1252
            // default is the first element of the list
1253
            // always created with the same sorting (DESCENDING)
1254
            if(isFirst) {
1255
                Map<String, String> defaultMap = new HashMap<String, String>();
1256
                defaultMap.put("default", classificationKey);
1257
                cmapList.add(defaultMap);
1258
                isFirst = false;
1259
            }
1260
            classifications.put(classificationKey, refTitleCache);
1261

    
1262
        }
1263
        Map<String, Map> cmap = new HashMap<String, Map>();
1264
        cmap.put("classification",classifications);
1265
        cmapList.add(cmap);
1266
        mv.addObject(cmapList);
1267
        return mv;
1268
    }
1269

    
1270
    /**
1271
     * Returns the Dataset ID / Name of the given original source.
1272
     * FIXME: Very hacky and needs to be revisited. Mainly for deciding on which objects to use during import.
1273
     * FIXME: dataset id is mapped to a DWC term - is that right?
1274
     * 
1275
     * @param sources Set of sources attached to a taxa / synonym
1276
     *             
1277
     *
1278
     * @return String array where [0] is the datsetID and [1] is the datsetName
1279
     */
1280
    private String[] getDatasetIdName(Set<IdentifiableSource> sources) {
1281
    	String didname[] = {"",""};
1282
        Iterator<IdentifiableSource> itr = sources.iterator();
1283
        while(itr.hasNext()) {
1284
        	IdentifiableSource source = itr.next();        	
1285
        	Reference ref = source.getCitation();        	   
1286
            Set<IdentifiableSource> ref_sources = ref.getSources();
1287
            Iterator<IdentifiableSource> ref_itr = ref_sources.iterator();
1288
            while(ref_itr.hasNext()) {
1289
            	IdentifiableSource ref_source = ref_itr.next();            	
1290
            	if(ref_source.getIdNamespace().equals(DWC_DATASET_ID)) {
1291
            		didname[0] = ref_source.getIdInSource();
1292
            		break;
1293
            	}
1294
            }
1295
            if(!didname[0].isEmpty()) {
1296
            	didname[1] = ref.getTitleCache();
1297
            	break;
1298
            }
1299
        }
1300
    	return didname;
1301
    }
1302
    
1303
    /**
1304
     * Returns the match mode by parsing the input string of wildcards.
1305
     *
1306
     * @param query
1307
     *             String to parse.
1308
     *
1309
     * @return {@link MatchMode} depending on the the position of the wildcard (*)
1310
     */
1311
    private MatchMode getMatchModeFromQuery(String query) {
1312
        if (query.startsWith("*") && query.endsWith("*")) {
1313
            return MatchMode.ANYWHERE;
1314
        } else if (query.startsWith("*")) {
1315
            return MatchMode.END;
1316
        } else if (query.endsWith("*")) {
1317
            return MatchMode.BEGINNING;
1318
        } else {
1319
            return MatchMode.EXACT;
1320
        }
1321
    }
1322

    
1323
    /**
1324
     * Removes wildcards from the input string.
1325
     *
1326
     * @param query
1327
     *             String to parse.
1328
     *
1329
     * @return input string with wildcards removed
1330
     */
1331
    private String getQueryWithoutWildCards(String query) {
1332

    
1333
        String newQuery = query;
1334

    
1335
        if (query.startsWith("*")) {
1336
            newQuery = newQuery.substring(1, newQuery.length());
1337
        }
1338

    
1339
        if (query.endsWith("*")) {
1340
            newQuery = newQuery.substring(0, newQuery.length() - 1);
1341
        }
1342

    
1343
        return newQuery.trim();
1344
    }
1345

    
1346
    /**
1347
     * Build map with taxon flag key-value pairs.
1348
     */
1349
    private Map<String, String> buildFlagMap(TaxonBase tb) {
1350
        Map<String, String> flags = new Hashtable<String, String>();
1351
        flags.put(DOUBTFUL_FLAG, Boolean.toString(tb.isDoubtful()));
1352
        return flags;
1353
    }
1354

    
1355
    /**
1356
     * Build classification map.
1357
     */
1358
    private Map<String, Map> getClassification(Taxon taxon, String classificationType, boolean includeUuids) {
1359
        // Using TreeMap is important, because we need the sorting of the classification keys
1360
        // in the map to be stable.
1361
        TreeMap<String, Map> sourceClassificationMap = buildClassificationMap(taxon, includeUuids);
1362

    
1363
        // if classification key is 'default' then return the default element of the map
1364
        if(classificationType.equals(CLASSIFICATION_DEFAULT) && !sourceClassificationMap.isEmpty()) {
1365
            List<Classification> clist = getClassificationList(1);
1366
            String defaultKey = removeInternalWhitespace(clist.get(0).getTitleCache());
1367
            return sourceClassificationMap.get(defaultKey);
1368
            // if classification key is provided then return the classification corresponding to the key
1369
        } else if(sourceClassificationMap.containsKey(classificationType)) {
1370
            return sourceClassificationMap.get(classificationType);
1371
            // if classification key is 'all' then return the entire map
1372
        } else if(classificationType.equals(CLASSIFICATION_ALL)) {
1373
            return sourceClassificationMap;
1374
        } else {
1375
            return new TreeMap<String,Map>();
1376
        }
1377
    }
1378

    
1379
    /**
1380
     * Build classification map.
1381
     */
1382
    private TreeMap<String, Map> buildClassificationMap(Taxon taxon, boolean includeUuid) {
1383
        // Using TreeMap is important, because we need the sorting of the classification keys
1384
        // in the map to be stable.
1385
        TreeMap<String, Map> sourceClassificationMap = new TreeMap<String, Map>();
1386
        Set<TaxonNode> taxonNodes = taxon.getTaxonNodes();
1387
        //loop through taxon nodes and build classification map for each classification key
1388
        for (TaxonNode tn : taxonNodes) {
1389
            Map<String, Object> classificationMap = new LinkedHashMap<String, Object>();
1390
            List<TaxonNode> tnList = classificationService.loadTreeBranchToTaxon(taxon,
1391
                    tn.getClassification(), null, TAXON_NODE_INIT_STRATEGY);
1392
            for (TaxonNode classificationtn : tnList) {
1393
            	if(includeUuid) {
1394
            		// creating map object with <name, uuid> elements
1395
            		Map<String, String> clMap = new HashMap<String, String>();
1396
            		clMap.put("name",classificationtn.getTaxon().getName().getTitleCache());
1397
            		clMap.put("uuid",classificationtn.getTaxon().getUuid().toString());
1398
            		classificationMap.put(classificationtn.getTaxon().getName().getRank().getTitleCache(), clMap);
1399
            	} else {
1400
            		classificationMap.put(classificationtn.getTaxon().getName().getRank().getTitleCache(), 
1401
            				classificationtn.getTaxon().getName().getTitleCache());
1402
            	}
1403
            }
1404
            String cname = removeInternalWhitespace(tn.getClassification().getTitleCache());
1405
            logger.info("Building classification map " + cname);
1406
            sourceClassificationMap.put(cname, classificationMap);
1407
        }
1408
        return sourceClassificationMap;
1409
    }
1410

    
1411
    private String removeInternalWhitespace(String withWSpace) {
1412
        String[] words = withWSpace.split("\\s+");
1413
        // "\\s+" in regular expression language meaning one or
1414
        // more spaces
1415
        StringBuilder builder = new StringBuilder();
1416
        for (String word : words) {
1417
            builder.append(word);
1418
        }
1419
        return builder.toString();
1420
    }
1421

    
1422
    private List<Classification> getClassificationList(int limit) {
1423
        List<OrderHint> orderHints = new ArrayList<OrderHint>();
1424
        orderHints.add(new OrderHint("titleCache", SortOrder.DESCENDING));
1425
        List<Classification> clist = classificationService.listClassifications(limit, 0, orderHints, VOC_CLASSIFICATION_INIT_STRATEGY);
1426
        return clist;
1427
    }
1428

    
1429
    private boolean isValid(String uuid){
1430
        if( uuid == null) return false;
1431
        try {
1432
            // we have to convert to object and back to string because the built in fromString does not have
1433
            // good validation logic.
1434

    
1435
            UUID fromStringUUID = UUID.fromString(uuid);
1436
            String toStringUUID = fromStringUUID.toString();
1437

    
1438
            System.out.println("input uuid : " + uuid + " , parsed uuid : " + toStringUUID);
1439
            return toStringUUID.equals(uuid);
1440
        } catch(IllegalArgumentException e) {
1441
            return false;
1442
        }
1443
    }
1444
    @Override
1445
    public void setResourceLoader(ResourceLoader resourceLoader) {
1446
        this.resourceLoader = resourceLoader;
1447

    
1448
    }
1449
}
(1-1/3)