Project

General

Profile

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

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

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

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

    
44
import com.wordnik.swagger.annotations.Api;
45

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

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

    
88
@Controller
89
@Api("name_catalogue")
90
@RequestMapping(value = { "/name_catalogue" })
91
public class NameCatalogueController extends AbstractController<TaxonNameBase, INameService> implements ResourceLoaderAware {
92

    
93
    private ResourceLoader resourceLoader;
94

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

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

    
101
    /** Flag 'doubtful' strings */
102
    public static final String DOUBTFUL_FLAG = "doubtful";
103

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

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

    
110
    /** Default name search type */
111
    public static final String DEFAULT_SEARCH_TYPE = NAME_SEARCH;
112

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

    
116
    /** Classifcation 'default' key */
117
    public static final String CLASSIFICATION_DEFAULT = "default";
118

    
119
    /** Classifcation 'all' key */
120
    public static final String CLASSIFICATION_ALL = "all";
121

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

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

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

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

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

    
135
    @Autowired
136
    private ITaxonService taxonService;
137

    
138

    
139
    @Autowired
140
    private IClassificationService classificationService;
141

    
142
    @Autowired
143
    private ICommonService commonService;
144

    
145
    /** Hibernate name search initialisation strategy */
146
    private static final List<String> NAME_SEARCH_INIT_STRATEGY = Arrays.asList(new String[] {
147
            "combinationAuthorship.$",
148
            "exCombinationAuthorship.$",
149
            "basionymAuthorship.$",
150
            "exBasionymAuthorship.$",
151
            "nameCache",
152
            "taxonBases",
153
            "taxonBases.synonymRelations.type.$"});
154

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

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

    
181
    /** Hibernate taxon information initialisation strategy */
182
    private static final List<String> TAXON_INFORMATION_INIT_STRATEGY = Arrays.asList(new String[] {
183
            "name.titleCache",
184
            "name.rank.titleCache",
185

    
186
            "sec.updated",
187
            "sec.titleCache",
188
            "sources.citation.sources.idNamespace",
189
            "sources.citation.sources.idInSource",
190

    
191
            "synonymRelations.synonym.name.rank.titleCache",
192
            "synonymRelations.synonym.sec.updated",
193
            "synonymRelations.synonym.sec.titleCache",
194
            "synonymRelations.synonym.sources.citation.sources.idNamespace",
195
            "synonymRelations.synonym.sources.citation.sources.idInSource",
196
            "synonymRelations.acceptedTaxon.name.rank.titleCache",
197
            "synonymRelations.acceptedTaxon.sec.titleCache",
198
            "synonymRelations.acceptedTaxon.sources.citation.sources.idNamespace",
199
            "synonymRelations.acceptedTaxon.sources.citation.sources.idInSource",
200
            "synonymRelations.type.inverseRepresentations",
201

    
202
            "relationsFromThisTaxon.type.inverseRepresentations",
203
            "relationsFromThisTaxon.toTaxon.name.rank.titleCache",
204
            "relationsFromThisTaxon.toTaxon.sec.updated",
205
            "relationsFromThisTaxon.toTaxon.sec.titleCache",
206
            "relationsFromThisTaxon.toTaxon.sources.citation.sources.idNamespace",
207
            "relationsFromThisTaxon.toTaxon.sources.citation.sources.idInSource",
208

    
209
            "relationsToThisTaxon.type.inverseRepresentations",
210
            "relationsToThisTaxon.fromTaxon.name.rank.titleCache",
211
            "relationsToThisTaxon.fromTaxon.sec.updated",
212
            "relationsToThisTaxon.fromTaxon.sec.titleCache",
213
            "relationsToThisTaxon.fromTaxon.sources.citation.sources.idNamespace",
214
            "relationsToThisTaxon.fromTaxon.sources.citation.sources.idInSource",
215

    
216
            "taxonNodes",
217
            "taxonNodes.classification" });
218

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

    
227
    /** Hibernate classification vocabulary initialisation strategy */
228
    private static final List<String> VOC_CLASSIFICATION_INIT_STRATEGY = Arrays.asList(new String[] {
229
            "classification",
230
            "classification.reference.$",
231
            "classification.reference.authorship.$" });
232

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

    
236
    public NameCatalogueController() {
237
        super();
238
        setInitializationStrategy(Arrays.asList(new String[] { "$" }));
239
    }
240

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

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

    
280
        HtmlView hv = new HtmlView();
281
        mv.setView(hv);
282
        return mv;
283
    }
284

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

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

    
348
        int h = 100;
349
        try {
350
            h = Integer.parseInt(hits);
351
        } catch(NumberFormatException nfe) {
352
            ErrorResponse er = new ErrorResponse();
353
            er.setErrorMessage("hits parameter is not a number");
354
            mv.addObject(er);
355
            return mv;
356
        }
357

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

    
373
            boolean wc = false;
374

    
375
            if(getMatchModeFromQuery(query) == MatchMode.BEGINNING) {
376
                wc = true;
377
            }
378
            logger.info("doGetNameSearch()" + request.getRequestURI() + " for query \"" + query);
379

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

    
397

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

    
403
                for (DocumentSearchResult searchResult : nameSearchList) {
404
                    for(Document doc : searchResult.getDocs()) {
405

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

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

    
437
        mv.addObject(nsList);
438
        return mv;
439
    }
440

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

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

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

    
571

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

    
577
                for (DocumentSearchResult searchResult : nameSearchList) {
578
                    for(Document doc : searchResult.getDocs()) {
579

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

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

    
611
        mv.addObject(nsList);
612
        return mv;
613
    }
614

    
615
    private String[] mergeSynAccTaxonUuids(String[] accTaxonUuids) {
616
        List<String> accTaxonUuidList = new ArrayList<String>();
617
        for(String accTaxonUuid : accTaxonUuids) {
618
            for(String uuidListAsString : accTaxonUuid.split(AcceptedTaxonBridge.ACCEPTED_TAXON_UUID_LIST_SEP)) {
619
                accTaxonUuidList.add(uuidListAsString);
620
            }
621
        }
622
        return accTaxonUuidList.toArray(new String[0]);
623

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

    
651
        HtmlView hv = new HtmlView();
652
        mv.setView(hv);
653
        return mv;
654
    }
655

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

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

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

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

    
742
        HtmlView hv = new HtmlView();
743
        mv.setView(hv);
744
        return mv;
745
    }
746

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

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

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

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

    
831
                    String secTitle = "" ;
832
                    String modified = "";
833
                    if(taxon.getSec() != null) {
834
                        secTitle = taxon.getSec().getTitleCache();
835
                        DateTime dt = taxon.getUpdated();
836
                        modified = fmt.print(dt);
837
                    }
838

    
839
                    Set<IdentifiableSource> sources = taxon.getSources();
840
                    String[] didname = getDatasetIdName(sources);
841

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

    
855

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
992
                    Set<SynonymRelationship> synRelationships = synonym.getSynonymRelations();
993
                    for (SynonymRelationship sr : synRelationships) {
994
                        Taxon accTaxon = sr.getAcceptedTaxon();
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
                        TaxonNameBase accnvn = accTaxon.getName();
1001
                        String name = accnvn.getTitleCache();
1002
                        String rank = accnvn.getRank().getTitleCache();
1003
                        String status = ACCEPTED_NAME_STATUS;
1004
                        String relLabel = sr.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
                }
1027
                tiList.add(ti);
1028
            } else {
1029
                ErrorResponse re = new ErrorResponse();
1030
                if(isValid(taxonUuid)) {
1031
                    re.setErrorMessage("No Taxon for given UUID : " + taxonUuid);
1032
                } else {
1033
                    re.setErrorMessage(taxonUuid + " not a valid UUID");
1034
                }
1035
                tiList.add(re);
1036
            }
1037
        }
1038
        mv.addObject(tiList);
1039
        return mv;
1040
    }
1041

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

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

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

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

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

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

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

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

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

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

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

    
1207
                        }
1208
                    }
1209
                    // update name search object
1210

    
1211
                }
1212
                ansList.add(ans);
1213

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

    
1221
        mv.addObject(ansList);
1222
        return mv;
1223
    }
1224

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

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

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

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

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

    
1337
        String newQuery = query;
1338

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

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

    
1347
        return newQuery.trim();
1348
    }
1349

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

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

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

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

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

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

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

    
1441
            UUID fromStringUUID = UUID.fromString(uuid);
1442
            String toStringUUID = fromStringUUID.toString();
1443

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

    
1454
    }
1455
}
(1-1/3)