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.time.ZonedDateTime;
14
import java.time.format.DateTimeFormatter;
15
import java.util.ArrayList;
16
import java.util.Arrays;
17
import java.util.HashMap;
18
import java.util.HashSet;
19
import java.util.Hashtable;
20
import java.util.Iterator;
21
import java.util.LinkedHashMap;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.Set;
25
import java.util.TreeMap;
26
import java.util.UUID;
27

    
28
import javax.servlet.http.HttpServletRequest;
29
import javax.servlet.http.HttpServletResponse;
30

    
31
import org.apache.lucene.document.Document;
32
import org.springframework.beans.factory.annotation.Autowired;
33
import org.springframework.context.ResourceLoaderAware;
34
import org.springframework.core.io.Resource;
35
import org.springframework.core.io.ResourceLoader;
36
import org.springframework.stereotype.Controller;
37
import org.springframework.web.bind.annotation.RequestMapping;
38
import org.springframework.web.bind.annotation.RequestMethod;
39
import org.springframework.web.bind.annotation.RequestParam;
40
import org.springframework.web.servlet.ModelAndView;
41

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

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

    
84
@Controller
85
@Api("name_catalogue")
86
@RequestMapping(value = { "/name_catalogue" })
87
public class NameCatalogueController extends AbstractController<TaxonName, INameService> implements ResourceLoaderAware {
88

    
89
    private ResourceLoader resourceLoader;
90

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

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

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

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

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

    
106
    /** Default name search type */
107
    public static final String DEFAULT_SEARCH_TYPE = NAME_SEARCH;
108

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

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

    
115
    /** Classifcation 'all' key */
116
    public static final String CLASSIFICATION_ALL = "all";
117

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

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

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

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

    
129
    private static final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy");
130

    
131
    @Autowired
132
    private ITaxonService taxonService;
133

    
134

    
135
    @Autowired
136
    private IClassificationService classificationService;
137

    
138
    @Autowired
139
    private ICommonService commonService;
140

    
141
    /** Hibernate name search initialisation strategy */
142
    private static final List<String> NAME_SEARCH_INIT_STRATEGY = Arrays.asList(new String[] {
143
            "combinationAuthorship.$",
144
            "exCombinationAuthorship.$",
145
            "basionymAuthorship.$",
146
            "exBasionymAuthorship.$",
147
            "nameCache",
148
            "taxonBases"});
149

    
150
    /** Hibernate accepted name search initialisation strategy */
151
    private static final List<String> ACC_NAME_SEARCH_INIT_STRATEGY = Arrays.asList(new String[] {
152
            "nameCache",
153
            "taxonBases",
154
            "taxonBases.acceptedTaxon.name.nameCache",
155
            "taxonBases.acceptedTaxon.name.rank.titleCache",
156
            "taxonBases.acceptedTaxon.taxonNodes.classification",
157
            "taxonBases.taxonNodes.classification",
158
            "taxonBases.relationsFromThisTaxon.type.$"});
159

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

    
176
    /** Hibernate taxon information initialisation strategy */
177
    private static final List<String> TAXON_INFORMATION_INIT_STRATEGY = Arrays.asList(new String[] {
178
            "name.titleCache",
179
            "name.rank.titleCache",
180

    
181
            "sec.updated",
182
            "sec.titleCache",
183
            "sources.citation.sources.idNamespace",
184
            "sources.citation.sources.idInSource",
185

    
186
            "synonyms.name.rank.titleCache",
187
            "synonyms.sec.updated",
188
            "synonyms.sec.titleCache",
189
            "synonyms.sources.citation.sources.idNamespace",
190
            "synonyms.sources.citation.sources.idInSource",
191
            "synonyms.type.inverseRepresentations",
192
            "acceptedTaxon.name.rank.titleCache",
193
            "acceptedTaxon.sec.titleCache",
194
            "acceptedTaxon.sources.citation.sources.idNamespace",
195
            "acceptedTaxon.sources.citation.sources.idInSource",
196

    
197
            "relationsFromThisTaxon.type.inverseRepresentations",
198
            "relationsFromThisTaxon.toTaxon.name.rank.titleCache",
199
            "relationsFromThisTaxon.toTaxon.sec.updated",
200
            "relationsFromThisTaxon.toTaxon.sec.titleCache",
201
            "relationsFromThisTaxon.toTaxon.sources.citation.sources.idNamespace",
202
            "relationsFromThisTaxon.toTaxon.sources.citation.sources.idInSource",
203

    
204
            "relationsToThisTaxon.type.inverseRepresentations",
205
            "relationsToThisTaxon.fromTaxon.name.rank.titleCache",
206
            "relationsToThisTaxon.fromTaxon.sec.updated",
207
            "relationsToThisTaxon.fromTaxon.sec.titleCache",
208
            "relationsToThisTaxon.fromTaxon.sources.citation.sources.idNamespace",
209
            "relationsToThisTaxon.fromTaxon.sources.citation.sources.idInSource",
210

    
211
            "taxonNodes",
212
            "taxonNodes.classification" });
213

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

    
222
    /** Hibernate classification vocabulary initialisation strategy */
223
    private static final List<String> VOC_CLASSIFICATION_INIT_STRATEGY = Arrays.asList(new String[] {
224
            "classification",
225
            "classification.reference.$",
226
            "classification.reference.authorship.$" });
227

    
228
    /** Hibernate classification vocabulary initialisation strategy */
229
    private static final List<String> COMMON_INIT_STRATEGY = Arrays.asList(new String[] {});
230

    
231
    public NameCatalogueController() {
232
        super();
233
        setInitializationStrategy(Arrays.asList(new String[] { "$" }));
234
    }
235

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

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

    
275
        HtmlView hv = new HtmlView();
276
        mv.setView(hv);
277
        return mv;
278
    }
279

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

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

    
343
        int h = 100;
344
        try {
345
            h = Integer.parseInt(hits);
346
        } catch(NumberFormatException nfe) {
347
            ErrorResponse er = new ErrorResponse();
348
            er.setErrorMessage("hits parameter is not a number");
349
            mv.addObject(er);
350
            return mv;
351
        }
352

    
353
        // search through each query
354
        for (String query : queries) {
355
            if(query.equals("")) {
356
                ErrorResponse er = new ErrorResponse();
357
                er.setErrorMessage("Empty query field");
358
                nsList.add(er);
359
                continue;
360
            }
361
            // remove wildcards if any
362
            String queryWOWildcards = getQueryWithoutWildCards(query);
363
            // convert first char to upper case
364
            char[] stringArray = queryWOWildcards.toCharArray();
365
            stringArray[0] = Character.toUpperCase(stringArray[0]);
366
            queryWOWildcards = new String(stringArray);
367

    
368
            boolean wc = false;
369

    
370
            if(getMatchModeFromQuery(query) == MatchMode.BEGINNING) {
371
                wc = true;
372
            }
373
            logger.info("doGetNameSearch()" + request.getRequestURI() + " for query \"" + query);
374

    
375
            List<DocumentSearchResult> nameSearchList = new ArrayList<DocumentSearchResult>();
376
            try {
377
                nameSearchList = service.findByNameExactSearch(
378
                        queryWOWildcards,
379
                        wc,
380
                        null,
381
                        false,
382
                        h);
383
            } catch (LuceneParseException e) {
384
                // TODO Auto-generated catch block
385
                //e.printStackTrace();
386
                ErrorResponse er = new ErrorResponse();
387
                er.setErrorMessage("Could not parse name : " + query);
388
                nsList.add(er);
389
                continue;
390
            }
391

    
392

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

    
398
                for (DocumentSearchResult searchResult : nameSearchList) {
399
                    for(Document doc : searchResult.getDocs()) {
400

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

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

    
432
        mv.addObject(nsList);
433
        return mv;
434
    }
435

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

    
462
        HtmlView hv = new HtmlView();
463
        mv.setView(hv);
464
        return mv;
465
    }
466
    /**
467
     * Returns a list of scientific names similar to the <code>{query}</code>
468
     * string pattern. Each of these scientific names is accompanied by a list of
469
     * name uuids, a list of taxon uuids and a list of accepted taxon uuids.
470
     * The underlying (Lucene FuzzyQuery) string distance metric used is based on a
471
     * fail-fast Levenshtein distance algorithm (is aborted if it is discovered that
472
     * the mimimum distance between the words is greater than some threshold)
473
     * <p>
474
     * Endpoint documentation can be found <a href="{@docRoot}/../remote/name-catalogue-fuzzy.html">here</a>
475
     * <p>
476
     * URI: <b>&#x002F;{datasource-name}&#x002F;name_catalogue/fuzzy</b>
477
     *
478
     * @param query
479
     *                The scientific name pattern(s) to query for. Any wildcard characters in the
480
     *                query are removed.
481
     * @param accuracy
482
     *                Similarity measure (between 0 and 1) to impose on the matching algorithm.
483
     *                Briefly described, this is equivalent to the edit distance between the two words, divided by
484
     *                the length of the shorter of the compared terms.
485
     * @param hits
486
     *            Maximum number of responses to be returned.
487
     * @param type
488
     *            The type of fuzzy search to call. This can be either
489
     *              "name" : fuzzy searches scientific names corresponding to 'name cache' in CDM or
490
     *              "atomised" : parses the query into atomised elements and fuzzy searches the individual elements in the CDM
491
     * @param request Http servlet request.
492
     * @param response Http servlet response.
493
     * @return a List of {@link NameSearch} objects each corresponding to a
494
     *         single query. These are built from {@link TaxonName} entities
495
     *         which are in turn initialized using the {@link #NAME_SEARCH_INIT_STRATEGY}
496
     * @throws IOException
497
     */
498
    @RequestMapping(value = { "fuzzy" }, method = RequestMethod.GET, params = {"query"})
499
    public ModelAndView doGetNameFuzzySearch(@RequestParam(value = "query", required = true) String[] queries,
500
            @RequestParam(value = "accuracy", required = false, defaultValue = "0.6") String accuracy,
501
            @RequestParam(value = "hits", required = false, defaultValue = "10") String hits,
502
            @RequestParam(value = "type", required = false, defaultValue = FUZZY_NAME_CACHE) String type,
503
            HttpServletRequest request, HttpServletResponse response) throws IOException {
504
        ModelAndView mv = new ModelAndView();
505
        List<RemoteResponse> nsList = new ArrayList<RemoteResponse>();
506
        float acc = 0.5f;
507
        int h = 10;
508
        try {
509
            acc = Float.parseFloat(accuracy);
510
            h = Integer.parseInt(hits);
511
        } catch(NumberFormatException nfe) {
512
            ErrorResponse er = new ErrorResponse();
513
            er.setErrorMessage("accuracy or hits parameter is not a number");
514
            mv.addObject(er);
515
            return mv;
516
        }
517

    
518
        if(acc < 0.0 || acc >= 1.0) {
519
            ErrorResponse er = new ErrorResponse();
520
            er.setErrorMessage("accuracy should be >= 0.0 and < 1.0");
521
            mv.addObject(er);
522
            return mv;
523
        }
524
        // search through each query
525
        for (String query : queries) {
526
            if(query.equals("")) {
527
                ErrorResponse er = new ErrorResponse();
528
                er.setErrorMessage("Empty query field");
529
                nsList.add(er);
530
                continue;
531
            }
532
            // remove wildcards if any
533
            String queryWOWildcards = getQueryWithoutWildCards(query);
534
            // convert first char to upper case
535
            char[] stringArray = queryWOWildcards.toCharArray();
536
            stringArray[0] = Character.toUpperCase(stringArray[0]);
537
            queryWOWildcards = new String(stringArray);
538
            logger.info("doGetNameSearch()" + request.getRequestURI() + " for query \"" + queryWOWildcards + " with accuracy " + accuracy);
539
            //List<NonViralName> nameList = new ArrayList<NonViralName>();
540
            List<DocumentSearchResult> nameSearchList = new ArrayList<DocumentSearchResult>();
541
            try {
542
                if(type.equals(FUZZY_ATOMISED)) {
543
                    nameSearchList = service.findByNameFuzzySearch(
544
                            queryWOWildcards,
545
                            acc,
546
                            null,
547
                            false,
548
                            h);
549
                } else {
550
                    nameSearchList = service.findByFuzzyNameCacheSearch(
551
                            queryWOWildcards,
552
                            acc,
553
                            null,
554
                            false,
555
                            h);
556
                }
557
            } catch (LuceneParseException e) {
558
                // TODO Auto-generated catch block
559
                //e.printStackTrace();
560
                ErrorResponse er = new ErrorResponse();
561
                er.setErrorMessage("Could not parse name : " + queryWOWildcards);
562
                nsList.add(er);
563
                continue;
564
            }
565

    
566

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

    
572
                for (DocumentSearchResult searchResult : nameSearchList) {
573
                    for(Document doc : searchResult.getDocs()) {
574

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

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

    
606
        mv.addObject(nsList);
607
        return mv;
608
    }
609

    
610
    private String[] mergeSynAccTaxonUuids(String[] accTaxonUuids) {
611
        List<String> accTaxonUuidList = new ArrayList<String>();
612
        for(String accTaxonUuid : accTaxonUuids) {
613
            for(String uuidListAsString : accTaxonUuid.split(AcceptedTaxonBridge.ACCEPTED_TAXON_UUID_LIST_SEP)) {
614
                accTaxonUuidList.add(uuidListAsString);
615
            }
616
        }
617
        return accTaxonUuidList.toArray(new String[0]);
618

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

    
646
        HtmlView hv = new HtmlView();
647
        mv.setView(hv);
648
        return mv;
649
    }
650

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

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

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

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

    
736
        HtmlView hv = new HtmlView();
737
        mv.setView(hv);
738
        return mv;
739
    }
740

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

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

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

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

    
825
                    String secTitle = "" ;
826
                    String modified = "";
827
                    if(taxon.getSec() != null) {
828
                        secTitle = taxon.getSec().getTitleCache();
829
                        ZonedDateTime dt = taxon.getUpdated();
830
                        modified = dt.format(fmt);
831
                    }
832

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

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

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

    
855

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

    
869
                        secTitle = "" ;
870
                        modified = "";
871
                        if(syn.getSec() != null) {
872
                            secTitle = syn.getSec().getTitleCache();
873
                            ZonedDateTime dt = syn.getUpdated();
874
                            modified = dt.format(fmt);
875
                        }
876

    
877
                        sources = syn.getSources();
878
                        didname = getDatasetIdName(sources);
879

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

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

    
906
                        secTitle = "" ;
907
                        modified = "";
908
                        if(tr.getToTaxon().getSec() != null) {
909
                            secTitle = tr.getToTaxon().getSec().getTitleCache();
910
                            ZonedDateTime dt = tr.getToTaxon().getUpdated();
911
                            modified = dt.format(fmt);
912
                        }
913

    
914
                        sources = tr.getToTaxon().getSources();
915
                        didname = getDatasetIdName(sources);
916

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

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

    
944
                        if(tr.getFromTaxon().getSec() != null) {
945
                            secTitle = tr.getFromTaxon().getSec().getTitleCache();
946
                            ZonedDateTime dt = tr.getFromTaxon().getSec().getUpdated();
947
                            modified = dt.format(fmt);
948
                        }
949

    
950
                        sources = tr.getFromTaxon().getSources();
951
                        didname = getDatasetIdName(sources);
952

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

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

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

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

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

    
1007
                        sources = accTaxon.getSources();
1008
                        didname = getDatasetIdName(sources);
1009

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

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

    
1065
        HtmlView hv = new HtmlView();
1066
        mv.setView(hv);
1067
        return mv;
1068
    }
1069

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

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

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

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

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

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

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

    
1167
                for (INonViralName nvn : nameList) {
1168
                    // we need to retrieve both taxon uuid of name queried and
1169
                    // the corresponding accepted taxa.
1170
                    // reason to return accepted taxa also, is to be able to get from
1171
                    // scientific name to taxon concept in two web service calls.
1172
                    Set<TaxonBase> tbSet = nvn.getTaxonBases();
1173
                    Set<TaxonBase> accTbSet = new HashSet<TaxonBase>();
1174
                    for (TaxonBase tb : tbSet) {
1175
                        // if synonym then get accepted taxa.
1176
                        if (tb instanceof Synonym) {
1177
                            Synonym synonym = (Synonym) tb;
1178
                            Taxon accTaxon = synonym.getAcceptedTaxon();
1179
                            if (accTaxon != null) {
1180
                                INonViralName accNvn = CdmBase.deproxy(accTaxon.getName());
1181
                                Map<String, 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> sourcesItr = sources.iterator();
1283
        while(sourcesItr.hasNext()) {
1284
            IdentifiableSource source = sourcesItr.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) {
1431
            return false;
1432
        }
1433
        try {
1434
            // we have to convert to object and back to string because the built in fromString does not have
1435
            // good validation logic.
1436

    
1437
            UUID fromStringUUID = UUID.fromString(uuid);
1438
            String toStringUUID = fromStringUUID.toString();
1439

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

    
1450
    }
1451
}
(1-1/3)