Project

General

Profile

Download (66.4 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.joda.time.DateTime;
31
import org.joda.time.format.DateTimeFormat;
32
import org.joda.time.format.DateTimeFormatter;
33
import org.springframework.beans.factory.annotation.Autowired;
34
import org.springframework.context.ResourceLoaderAware;
35
import org.springframework.core.io.Resource;
36
import org.springframework.core.io.ResourceLoader;
37
import org.springframework.stereotype.Controller;
38
import org.springframework.web.bind.annotation.RequestMapping;
39
import org.springframework.web.bind.annotation.RequestMethod;
40
import org.springframework.web.bind.annotation.RequestParam;
41
import org.springframework.web.servlet.ModelAndView;
42

    
43
import eu.etaxonomy.cdm.api.service.IClassificationService;
44
import eu.etaxonomy.cdm.api.service.ICommonService;
45
import eu.etaxonomy.cdm.api.service.INameService;
46
import eu.etaxonomy.cdm.api.service.ITaxonService;
47
import eu.etaxonomy.cdm.api.service.search.DocumentSearchResult;
48
import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
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.INonViralName;
55
import eu.etaxonomy.cdm.model.name.TaxonName;
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.Taxon;
60
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
61
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
62
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
63
import eu.etaxonomy.cdm.persistence.query.MatchMode;
64
import eu.etaxonomy.cdm.persistence.query.OrderHint;
65
import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
66
import eu.etaxonomy.cdm.remote.controller.AbstractController;
67
import eu.etaxonomy.cdm.remote.dto.common.ErrorResponse;
68
import eu.etaxonomy.cdm.remote.dto.common.RemoteResponse;
69
import eu.etaxonomy.cdm.remote.dto.namecatalogue.AcceptedNameSearch;
70
import eu.etaxonomy.cdm.remote.dto.namecatalogue.NameInformation;
71
import eu.etaxonomy.cdm.remote.dto.namecatalogue.NameSearch;
72
import eu.etaxonomy.cdm.remote.dto.namecatalogue.TaxonInformation;
73
import eu.etaxonomy.cdm.remote.view.HtmlView;
74
import io.swagger.annotations.Api;
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
@Api("name_catalogue")
88
@RequestMapping(value = { "/name_catalogue" })
89
public class NameCatalogueController extends AbstractController<TaxonName, INameService> implements ResourceLoaderAware {
90

    
91
    private ResourceLoader resourceLoader;
92

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

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

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

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

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

    
108
    /** Default name search type */
109
    public static final String DEFAULT_SEARCH_TYPE = NAME_SEARCH;
110

    
111
    /** Default max number of hits for the exact name search  */
112
    public static final String DEFAULT_MAX_NB_FOR_EXACT_SEARCH = "100";
113

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

    
117
    /** Classifcation 'all' key */
118
    public static final String CLASSIFICATION_ALL = "all";
119

    
120
    /** Classification to include uuids key */
121
    public static final String INCLUDE_CLUUIDS = "cluuids";
122

    
123
    /** Fuzzy Name Cache search */
124
    public static final String FUZZY_NAME_CACHE = "name";
125

    
126
    /** Fuzzy Atomised Name search */
127
    public static final String FUZZY_ATOMISED = "atomised";
128

    
129
    private static final String DWC_DATASET_ID = "http://rs.tdwg.org/dwc/terms/datasetID";
130

    
131
    private static final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd-MM-yyyy");
132

    
133
    @Autowired
134
    private ITaxonService taxonService;
135

    
136

    
137
    @Autowired
138
    private IClassificationService classificationService;
139

    
140
    @Autowired
141
    private ICommonService commonService;
142

    
143
    /** Hibernate name search initialisation strategy */
144
    private static final List<String> NAME_SEARCH_INIT_STRATEGY = Arrays.asList(new String[] {
145
            "combinationAuthorship.$",
146
            "exCombinationAuthorship.$",
147
            "basionymAuthorship.$",
148
            "exBasionymAuthorship.$",
149
            "nameCache",
150
            "taxonBases"});
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.acceptedTaxon.name.nameCache",
157
            "taxonBases.acceptedTaxon.name.rank.titleCache",
158
            "taxonBases.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
            "combinationAuthorship.$",
168
            "exCombinationAuthorship.$",
169
            "basionymAuthorship.$",
170
            "exBasionymAuthorship.$",
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
            "synonyms.name.rank.titleCache",
189
            "synonyms.sec.updated",
190
            "synonyms.sec.titleCache",
191
            "synonyms.sources.citation.sources.idNamespace",
192
            "synonyms.sources.citation.sources.idInSource",
193
            "synonyms.type.inverseRepresentations",
194
            "acceptedTaxon.name.rank.titleCache",
195
            "acceptedTaxon.sec.titleCache",
196
            "acceptedTaxon.sources.citation.sources.idNamespace",
197
            "acceptedTaxon.sources.citation.sources.idInSource",
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.authorship.$" });
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.authorship.$" });
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 TaxonName}
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,RequestMethod.POST} , 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 TaxonName} entities
334
     *         which are in turn initialized using the {@link #NAME_SEARCH_INIT_STRATEGY}
335
     * @throws IOException
336
     */
337
    @RequestMapping(value = { "" }, method = {RequestMethod.GET,RequestMethod.POST}, 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 (LuceneParseException 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 TaxonName} 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 (LuceneParseException 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 TaxonName} 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,RequestMethod.POST}, 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
            TaxonName nvn = service.load(UUID.fromString(nameUuid),NAME_INFORMATION_INIT_STRATEGY);
681

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

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

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

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

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

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

    
812
            // if search is successful then get related information, else return error
813
            if (tb != null) {
814
                TaxonInformation ti = new TaxonInformation();
815
                ti.setRequest(taxonUuid);
816
                // check if result (taxon base) is a taxon or synonym
817
                if (tb.isInstanceOf(Taxon.class)) {
818
                    Taxon taxon = (Taxon) tb;
819
                    // build classification map
820
                    boolean includeUuids = Arrays.asList(includes).contains(INCLUDE_CLUUIDS);
821
                    Map<String,Map> classificationMap = getClassification(taxon, classificationType, includeUuids);
822

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

    
827
                    String secTitle = "" ;
828
                    String modified = "";
829
                    if(taxon.getSec() != null) {
830
                        secTitle = taxon.getSec().getTitleCache();
831
                        DateTime dt = taxon.getUpdated();
832
                        modified = fmt.print(dt);
833
                    }
834

    
835
                    Set<IdentifiableSource> sources = taxon.getSources();
836
                    String[] didname = getDatasetIdName(sources);
837

    
838
                    String lsidString = null;
839
                    if( taxon.getLsid() != null) {
840
                        lsidString = taxon.getLsid().toString();
841
                    }
842

    
843
                    ti.setResponseTaxon(tb.getTitleCache(),
844
                            nvn.getTitleCache(),
845
                            nvn.getRank().getTitleCache(),
846
                            ACCEPTED_NAME_STATUS,
847
                            buildFlagMap(tb),
848
                            classificationMap,
849
                            "",
850
                            didname[0],
851
                            didname[1],
852
                            secTitle,
853
                            modified,
854
                            lsidString
855
                     );
856

    
857

    
858
                    Set<Synonym> syns = taxon.getSynonyms();
859
                    // add synonyms (if exists) to taxon information object
860
                    for (Synonym syn : syns) {
861
                        String uuid = syn.getUuid().toString();
862
                        String title = syn.getTitleCache();
863
                        TaxonName synnvn = syn.getName();
864
                        String name = synnvn.getTitleCache();
865
                        String rank = (synnvn.getRank() == null)? "" : synnvn.getRank().getTitleCache();
866
                        String status = SYNONYM_STATUS;
867
                        String relLabel = syn.getType()
868
                                .getInverseRepresentation(Language.DEFAULT())
869
                                .getLabel();
870

    
871
                        secTitle = "" ;
872
                        modified = "";
873
                        if(syn.getSec() != null) {
874
                            secTitle = syn.getSec().getTitleCache();
875
                            DateTime dt = syn.getUpdated();
876
                            modified = fmt.print(dt);
877
                        }
878

    
879
                        sources = syn.getSources();
880
                        didname = getDatasetIdName(sources);
881

    
882
                        ti.addToResponseRelatedTaxa(uuid,
883
                                title,
884
                                name,
885
                                rank,
886
                                status,
887
                                relLabel,
888
                                "",
889
                                didname[0],
890
                                didname[1],
891
                                secTitle,
892
                                modified);
893
                    }
894

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

    
908
                        secTitle = "" ;
909
                        modified = "";
910
                        if(tr.getToTaxon().getSec() != null) {
911
                            secTitle = tr.getToTaxon().getSec().getTitleCache();
912
                            DateTime dt = tr.getToTaxon().getUpdated();
913
                            modified = fmt.print(dt);
914
                        }
915

    
916
                        sources = tr.getToTaxon().getSources();
917
                        didname = getDatasetIdName(sources);
918

    
919
                        ti.addToResponseRelatedTaxa(uuid,
920
                                titleTo,
921
                                name,
922
                                rank,
923
                                status,
924
                                relLabel,
925
                                "",
926
                                didname[0],
927
                                didname[1],
928
                                secTitle,
929
                                modified);
930
                        //logger.info("titleTo : " + titleTo + " , name : " + name);
931
                    }
932

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

    
946
                        if(tr.getFromTaxon().getSec() != null) {
947
                            secTitle = tr.getFromTaxon().getSec().getTitleCache();
948
                            DateTime dt = tr.getFromTaxon().getSec().getUpdated();
949
                            modified = fmt.print(dt);
950
                        }
951

    
952
                        sources = tr.getFromTaxon().getSources();
953
                        didname = getDatasetIdName(sources);
954

    
955
                        secTitle = (tr.getFromTaxon().getSec() == null) ? "" : tr.getFromTaxon().getSec().getTitleCache();
956
                        ti.addToResponseRelatedTaxa(uuid,
957
                                titleFrom,
958
                                name,
959
                                rank,
960
                                status,
961
                                relLabel,
962
                                "",
963
                                didname[0],
964
                                didname[1],
965
                                secTitle,
966
                                modified);
967
                        //logger.info("titleFrom : " + titleFrom + " , name : " + name);
968
                    }
969
                } else if (tb instanceof Synonym) {
970
                    Synonym synonym = (Synonym) tb;
971
                    TaxonName nvn = synonym.getName();
972
                 // update taxon information object with synonym related data
973
                    DateTime dt = synonym.getUpdated();
974
                    String modified = fmt.print(dt);
975

    
976
                    Set<IdentifiableSource> sources = synonym.getSources();
977
                    String[] didname = getDatasetIdName(sources);
978

    
979
                    String secTitle = (synonym.getSec() == null) ? "" : synonym.getSec().getTitleCache();
980
                    ti.setResponseTaxon(synonym.getTitleCache(),
981
                            nvn.getTitleCache(),
982
                            nvn.getRank().getTitleCache(),
983
                            SYNONYM_STATUS,
984
                            buildFlagMap(synonym),
985
                            new TreeMap<String,Map>(),
986
                            "",
987
                            didname[0],
988
                            didname[1],
989
                            secTitle,
990
                            modified, null);
991
                    // add accepted taxa (if exists) to taxon information object
992

    
993
                    Taxon accTaxon = synonym.getAcceptedTaxon();
994
                    if (accTaxon != null){
995
                        String uuid = accTaxon.getUuid().toString();
996
                        logger.info("acc taxon uuid " + accTaxon.getUuid().toString() + " original hash code : " + System.identityHashCode(accTaxon) + ", name class " + accTaxon.getName().getClass().getName());
997
                        String title = accTaxon.getTitleCache();
998
                        logger.info("taxon title cache : " + accTaxon.getTitleCache());
999

    
1000
                        TaxonName accnvn = accTaxon.getName();
1001
                        String name = accnvn.getTitleCache();
1002
                        String rank = accnvn.getRank().getTitleCache();
1003
                        String status = ACCEPTED_NAME_STATUS;
1004
                        String relLabel = synonym.getType().getRepresentation(Language.DEFAULT())
1005
                                .getLabel();
1006
                        dt = accTaxon.getUpdated();
1007
                        modified = fmt.print(dt);
1008

    
1009
                        sources = accTaxon.getSources();
1010
                        didname = getDatasetIdName(sources);
1011

    
1012
                        secTitle = (accTaxon.getSec() == null) ? "" : accTaxon.getSec().getTitleCache();
1013
                        ti.addToResponseRelatedTaxa(uuid,
1014
                                title,
1015
                                name,
1016
                                rank,
1017
                                status,
1018
                                relLabel,
1019
                                "",
1020
                                didname[0],
1021
                                didname[1],
1022
                                secTitle,
1023
                                modified);
1024
                    }
1025
                }
1026
                tiList.add(ti);
1027
            } else {
1028
                ErrorResponse re = new ErrorResponse();
1029
                if(isValid(taxonUuid)) {
1030
                    re.setErrorMessage("No Taxon for given UUID : " + taxonUuid);
1031
                } else {
1032
                    re.setErrorMessage(taxonUuid + " not a valid UUID");
1033
                }
1034
                tiList.add(re);
1035
            }
1036
        }
1037
        mv.addObject(tiList);
1038
        return mv;
1039
    }
1040

    
1041
    /**
1042
     * Returns a documentation page for the Accepted Name Search API.
1043
     * <p>
1044
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/accepted</b>
1045
     *
1046
     * @param request Http servlet request.
1047
     * @param response Http servlet response.
1048
     * @return Html page describing the Accepted Name Search API
1049
     * @throws IOException
1050
     */
1051
    @RequestMapping(value = { "accepted" }, method = RequestMethod.GET, params = {})
1052
    public ModelAndView doGetAcceptedNameSearchDocumentation(
1053
            HttpServletRequest request, HttpServletResponse response)
1054
            throws IOException {
1055
        ModelAndView mv = new ModelAndView();
1056
        // Read apt documentation file.
1057
        Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/name-catalogue-accepted.apt");
1058
        // using input stream as this works for both files in the classes directory
1059
        // as well as files inside jars
1060
        InputStream aptInputStream = resource.getInputStream();
1061
        // Build Html View
1062
        Map<String, String> modelMap = new HashMap<String, String>();
1063
        // Convert Apt to Html
1064
        modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
1065
        mv.addAllObjects(modelMap);
1066

    
1067
        HtmlView hv = new HtmlView();
1068
        mv.setView(hv);
1069
        return mv;
1070
    }
1071

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

    
1135
        // if search type is not known then return error
1136
        if (!searchType.equals(NAME_SEARCH) && !searchType.equals(TITLE_SEARCH)) {
1137
            ErrorResponse er = new ErrorResponse();
1138
            er.setErrorMessage("searchType parameter can only be set as" + NAME_SEARCH + " or "
1139
                    + TITLE_SEARCH);
1140
            mv.addObject(er);
1141
            return mv;
1142
        }
1143

    
1144
        // search through each query
1145
        for (String query : queries) {
1146

    
1147
            //String queryWOWildcards = getQueryWithoutWildCards(query);
1148
            //MatchMode mm = getMatchModeFromQuery(query);
1149
            logger.info("doGetAcceptedNameSearch()" + request.getRequestURI() + " for query \"" + query);
1150
            List<TaxonName> nameList = new ArrayList<>();
1151

    
1152
            // if "name" search then find by name cache
1153
            if (searchType.equals(NAME_SEARCH)) {
1154
                nameList = service.findNamesByNameCache(query, MatchMode.EXACT,
1155
                        ACC_NAME_SEARCH_INIT_STRATEGY);
1156
            }
1157

    
1158
            //if "title" search then find by title cache
1159
            if (searchType.equals(TITLE_SEARCH)) {
1160
                nameList = service.findNamesByTitleCache(query, MatchMode.EXACT,
1161
                        ACC_NAME_SEARCH_INIT_STRATEGY);
1162
            }
1163

    
1164
            // if search is successful then get related information , else return error
1165
            if (nameList == null || !nameList.isEmpty()) {
1166
                AcceptedNameSearch ans = new AcceptedNameSearch();
1167
                ans.setRequest(query);
1168

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

    
1197
                                    }
1198
                                }
1199
                            }
1200
                            if(isConceptRelationship) {
1201
                                Map classificationMap = getClassification(taxon, CLASSIFICATION_DEFAULT, false);
1202
                                ans.addToResponseList(nvn.getNameCache(), nvn.getAuthorshipCache(),nvn.getRank().getTitleCache(), classificationMap);
1203
                            }
1204

    
1205
                        }
1206
                    }
1207
                    // update name search object
1208

    
1209
                }
1210
                ansList.add(ans);
1211

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

    
1219
        mv.addObject(ansList);
1220
        return mv;
1221
    }
1222

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

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

    
1272
    /**
1273
     * Returns the Dataset ID / Name of the given original source.
1274
     * FIXME: Very hacky and needs to be revisited. Mainly for deciding on which objects to use during import.
1275
     * FIXME: dataset id is mapped to a DWC term - is that right?
1276
     *
1277
     * @param sources Set of sources attached to a taxa / synonym
1278
     *
1279
     *
1280
     * @return String array where [0] is the datsetID and [1] is the datsetName
1281
     */
1282
    private String[] getDatasetIdName(Set<IdentifiableSource> sources) {
1283
        String didname[] = {"",""};
1284
        Iterator<IdentifiableSource> itr = sources.iterator();
1285
        while(itr.hasNext()) {
1286
            IdentifiableSource source = itr.next();
1287
            Reference ref = source.getCitation();
1288
            Set<IdentifiableSource> ref_sources = ref.getSources();
1289
            Iterator<IdentifiableSource> ref_itr = ref_sources.iterator();
1290
            while(ref_itr.hasNext()) {
1291
                IdentifiableSource ref_source = ref_itr.next();
1292
                if(ref_source.getIdNamespace().equals(DWC_DATASET_ID)) {
1293
                    didname[0] = ref_source.getIdInSource();
1294
                    break;
1295
                }
1296
            }
1297
            if(!didname[0].isEmpty()) {
1298
                didname[1] = ref.getTitleCache();
1299
                break;
1300
            }
1301
        }
1302
        return didname;
1303
    }
1304

    
1305
    /**
1306
     * Returns the match mode by parsing the input string of wildcards.
1307
     *
1308
     * @param query
1309
     *             String to parse.
1310
     *
1311
     * @return {@link MatchMode} depending on the the position of the wildcard (*)
1312
     */
1313
    private MatchMode getMatchModeFromQuery(String query) {
1314
        if (query.startsWith("*") && query.endsWith("*")) {
1315
            return MatchMode.ANYWHERE;
1316
        } else if (query.startsWith("*")) {
1317
            return MatchMode.END;
1318
        } else if (query.endsWith("*")) {
1319
            return MatchMode.BEGINNING;
1320
        } else {
1321
            return MatchMode.EXACT;
1322
        }
1323
    }
1324

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

    
1335
        String newQuery = query;
1336

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

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

    
1345
        return newQuery.trim();
1346
    }
1347

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

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

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

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

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

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

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

    
1439
            UUID fromStringUUID = UUID.fromString(uuid);
1440
            String toStringUUID = fromStringUUID.toString();
1441

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

    
1452
    }
1453
}
(1-1/3)