Project

General

Profile

Download (36.8 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.ext.ipni;
10

    
11
import java.io.BufferedReader;
12
import java.io.IOException;
13
import java.io.InputStream;
14
import java.io.InputStreamReader;
15
import java.net.HttpURLConnection;
16
import java.net.MalformedURLException;
17
import java.net.URISyntaxException;
18
import java.net.URL;
19
import java.text.ParseException;
20
import java.util.ArrayList;
21
import java.util.HashMap;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.regex.Matcher;
25
import java.util.regex.Pattern;
26

    
27
import javax.validation.constraints.NotNull;
28

    
29
import org.apache.commons.lang.StringUtils;
30
import org.apache.http.HttpResponse;
31
import org.apache.log4j.Logger;
32
import org.springframework.stereotype.Component;
33

    
34
import eu.etaxonomy.cdm.api.application.ICdmRepository;
35
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
36
import eu.etaxonomy.cdm.common.CdmUtils;
37
import eu.etaxonomy.cdm.common.URI;
38
import eu.etaxonomy.cdm.common.UriUtils;
39
import eu.etaxonomy.cdm.model.agent.Person;
40
import eu.etaxonomy.cdm.model.agent.Team;
41
import eu.etaxonomy.cdm.model.common.Annotation;
42
import eu.etaxonomy.cdm.model.common.AnnotationType;
43
import eu.etaxonomy.cdm.model.common.Extension;
44
import eu.etaxonomy.cdm.model.common.ExtensionType;
45
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
46
import eu.etaxonomy.cdm.model.common.Language;
47
import eu.etaxonomy.cdm.model.common.TimePeriod;
48
import eu.etaxonomy.cdm.model.common.VerbatimTimePeriod;
49
import eu.etaxonomy.cdm.model.name.IBotanicalName;
50
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
51
import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
52
import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
53
import eu.etaxonomy.cdm.model.name.Rank;
54
import eu.etaxonomy.cdm.model.name.TaxonName;
55
import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
56
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
57
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
58
import eu.etaxonomy.cdm.model.reference.Reference;
59
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
60
import eu.etaxonomy.cdm.strategy.exceptions.StringNotParsableException;
61
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
62
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
63
import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
64

    
65
/**
66
 * @author a.mueller
67
 * @since Aug 16, 2010
68
 *
69
 * TODO the whole ipni service should be refactored to use UriUtils. I did this for the queryService method only because we ran into timeout
70
 * problems in tests. UriUtils handles these problems already.
71
 */
72
@Component
73
public class IpniService  implements IIpniService{
74
    private static final Logger logger = Logger.getLogger(IpniService.class);
75

    
76

    
77
    //TYPE
78
    private static final String EAST_OR_WEST = "East or west";
79

    
80
	private static final String NORTH_OR_SOUTH = "North or south";
81

    
82
	private static final String LATITUDE_SECONDS = "Latitude seconds";
83

    
84
	private static final String LATITUDE_MINUTES = "Latitude minutes";
85

    
86
	private static final String LATITUDE_DEGREES = "Latitude degrees";
87

    
88
	private static final String LONGITUDE_SECONDS = "Longitude seconds";
89

    
90
    private static final String LONGITUDE_MINUTES = "Longitude minutes";
91

    
92
    private static final String LONGITUDE_DEGREES = "Longitude degrees";
93

    
94

    
95

    
96
	private static final String COLLECTION_DATE_AS_TEXT = "Collection date as text";
97

    
98
	private static final String COLLECTION_DAY1 = "Collection day1";
99

    
100
	private static final String COLLECTION_MONTH1 = "Collection month1";
101

    
102
	private static final String COLLECTION_YEAR1 = "Collection year1";
103

    
104
	private static final String COLLECTION_DAY2 = "Collection day2";
105

    
106
	private static final String COLLECTION_MONTH2 = "Collection month2";
107

    
108
	private static final String COLLECTION_YEAR2 = "Collection year2";
109

    
110
	private static final String COLLECTION_NUMBER = "Collection number";
111

    
112
	private static final String COLLECTOR_TEAM_AS_TEXT = "Collector team as text";
113

    
114
	private static final String LOCALITY = "Locality";
115

    
116
	private static final String TYPE_REMARKS = "Type remarks";
117

    
118

    
119
	// GENERAL
120
	public static String ID = "Id";
121
	public static String VERSION = "Version";
122
	public static final String REMARKS = "Remarks";
123

    
124
	//NAMES
125
	public static final String FULL_NAME_WITHOUT_FAMILY_AND_AUTHORS = "Full name without family and authors";
126
	public static final String AUTHORS = "Authors";
127
	public static final String FAMILY = "Family";
128
	public static final String GENUS = "Genus";
129
	public static final String INFRA_GENUS = "Infra genus";
130
	public static final String SPECIES = "Species";
131
	public static final String INFRA_SPECIFIC = "Infra species";
132
	public static final String HYBRID = "Hybrid";
133
	public static final String RANK = "Rank";
134
	public static final String BASIONYM_AUTHOR = "Basionym author";
135
	public static final String PUBLISHING_AUTHOR = "Publishing author";
136
	public static final String PUBLICATION = "Publication";
137
	public static final String COLLATION = "Collation";
138
	public static final String PUBLICATION_YEAR_FULL = "Publication year full";
139
	public static final String NAME_STATUS = "Name status";
140
	public static final String BASIONYM = "Basionym";
141
	public static final String REPLACED_SYNONYM = "Replaced synonym";
142

    
143

    
144
	//AUTHORS
145

    
146
	public static final String STANDARD_FORM = "Standard Form";
147

    
148
	public static final String DEFAULT_AUTHOR_FORENAME = "Default author forename";
149
	public static final String DEFAULT_AUTHOR_SURNAME = "Default author surname";
150
	public static final String TAXON_GROUPS = "Taxon groups";
151
	public static final String DATES = "Dates";
152
	public static final String ALTERNATIVE_NAMES = "Alternative names";
153

    
154
	public static final String DEFAULT_AUTHOR_NAME = "Default author name";
155

    
156
	public static final String NAME_NOTES = "Name notes";
157
	public static final String NAME_SOURCE = "Name source";
158
	public static final String DATE_TYPE_CODE = "Date type code";
159
	public static final String DATE_TYPE_STRING = "Date type string";
160

    
161
	public static final String ALTERNATIVE_ABBREVIATIONS = "Alternative abbreviations";
162
	public static final String EXAMPLE_OF_NAME_PUBLISHED = "Example of name published";
163

    
164

    
165
	//PUBLICATIONS
166

    
167
	public static final String ABBREVIATION = "Abbreviation";
168
	public static final String TITLE = "Title";
169
	public static final String BPH_NUMBER = "BPH number";
170
	public static final String ISBN = "ISBN";
171
	public static final String ISSN = "ISSN";
172
	public static final String AUTHORS_ROLE = "Authors role";
173
	public static final String EDITION = "Edition";
174
	public static final String DATE = "Date";
175
	public static final String IN_PUBLICATION_FACADE = "In publication facade";
176
	public static final String LC_NUMBER = "LC number";
177
	public static final String PLACE = "Place";
178
	public static final String PUBLICATION_AUTHOR_TEAM = "Publication author team";
179
	public static final String PRECEDED_BY = "Preceded";
180
	public static final String TL2_AUTHOR = "TL2 author";
181
	public static final String TL2_NUMBER = "TL2 number";
182
	public static final String TDWG_ABBREVIATION = "TDWG abbreviation";
183

    
184
	private enum ServiceType{
185
		 AUTHOR,
186
		 NAME,
187
		 PUBLICATION,
188
		 ID
189
	}
190

    
191
	public enum IpniRank{
192
		ALL ("All"),
193
		FAMILIAL ("Familial"),
194
		INFRA_FAMILIAL ("Infrafamilial"),
195
		GENERIC("Generic"),
196
		INFRA_GENERIC("Infrageneric"),
197
		SPECIFIC ("Specific"),
198
		INFRA_SPECIFIC("InfraSpecific");
199

    
200
		String strRank;
201
		IpniRank(String strRank){
202
			this.strRank = strRank;
203
		}
204

    
205
		public static IpniRank valueOf(Rank rank){
206
			if (rank == null){
207
				return ALL;
208
			}else if (rank.isInfraSpecific()){
209
				return INFRA_SPECIFIC;
210
			}else if (rank.isSpecies()){
211
				return SPECIFIC;
212
			}else if (rank.isInfraGeneric()){
213
				return INFRA_GENERIC;
214
			}else if (rank.isGenus()){
215
				return GENERIC;
216
			}else if (rank.isLower(Rank.FAMILY())){
217
				return INFRA_FAMILIAL;
218
			}else if (rank.isHigher(Rank.SUBFAMILY())){
219
				return FAMILIAL;
220
			}else{
221
				logger.warn("Rank could not be transformed to ipni rank. Use ALL instead");
222
				return ALL;
223
			}
224
		}
225
	}
226

    
227

    
228
//	private URL serviceUrl;
229

    
230
// ******************************** CONSTRUCTOR **************************************
231

    
232
// ****************************** METHODS ****************************************************/
233

    
234
	@Override
235
    public List<Person> getAuthors(String abbreviation, String surname, String forename, String isoCountry, ICdmRepository services, IpniServiceAuthorConfigurator config){
236
		//config
237
		if (config == null){
238
			config = new IpniServiceAuthorConfigurator();
239
		}
240

    
241

    
242
		abbreviation = normalizeParameter(abbreviation);
243
		surname = normalizeParameter(surname);
244
		isoCountry = normalizeParameter(isoCountry);
245
		forename = normalizeParameter(forename);
246

    
247
		DelimitedFormat format = config.getFormat();
248

    
249
		String request = "find_abbreviation=" + abbreviation +
250
						"&find_surname=" + surname +
251
						"&find_isoCountry=" + isoCountry +
252
						"&find_forename=" + forename +
253
						"&output_format=" + format.parameter;
254

    
255
		return (List)queryService(request, services, getServiceUrl(IIpniService.AUTHOR_SERVICE_URL), config, ServiceType.AUTHOR);
256
	}
257

    
258

    
259
	/**
260
	 *	FIXME rewrote this method to rely on {@link UriUtils}. The whole class should be
261
	 *  adjusted to reflect this change.
262
	 *	Also see comments in the class' documentation block.
263
	 *
264
	 * @param restRequest
265
	 * @return
266
	*/
267
	private List<? extends IdentifiableEntity> queryService(String request, ICdmRepository repository, URL serviceUrl,
268
	            IIpniServiceConfigurator config, ServiceType serviceType){
269
		if (config == null){
270
			throw new NullPointerException("Ipni service configurator should not be null");
271
		}
272
		try {
273

    
274
            // create the request url
275
            URL newUrl = new URL(serviceUrl.getProtocol(),
276
                                                     serviceUrl.getHost(),
277
                                                     serviceUrl.getPort(),
278
                                                     serviceUrl.getPath()
279
                                                     + "?" + request);
280

    
281
            URI newUri = URI.fromUrl(newUrl);
282
            logger.info("Firing request for URI: " + newUri);
283
            HttpResponse response = UriUtils.getResponse(newUri, null);
284

    
285
            int responseCode = response.getStatusLine().getStatusCode();
286

    
287
            // get the content at the resource
288
            InputStream content = response.getEntity().getContent();
289
            // build the result
290
            List<? extends IdentifiableEntity<?>> result;
291
            if (serviceType.equals(ServiceType.AUTHOR)){
292
            	result = buildAuthorList(content, repository, config);
293
            }else if (serviceType.equals(ServiceType.NAME)){
294
            	result = buildNameList(content, repository, config);
295
            }else {
296
            	result = buildPublicationList(content, repository, config);
297
            }
298
            if(responseCode == HttpURLConnection.HTTP_OK){
299
                    return result;
300
            }else{
301
                //TODO error handling
302
            	logger.error("No Http_OK");
303
            }
304

    
305
        } catch (IOException e) {
306
            logger.error("No content for request: " + request);
307
        } catch (URISyntaxException e) {
308
			logger.error("Given URL could not be transformed into URI", e);
309
		}
310

    
311
        // error
312
        return null;
313
    }
314

    
315
	public InputStream queryServiceForID (String request, URL serviceUrl){
316

    
317
		try {
318

    
319
            // create the request url
320
            URL newUrl = new URL(serviceUrl.getProtocol(),
321
                                                     serviceUrl.getHost(),
322
                                                     serviceUrl.getPort(),
323
                                                     serviceUrl.getPath()
324
                                                     + "?" + request);
325

    
326
            URI newUri = URI.fromUrl(newUrl);
327
            logger.info("Firing request for URI: " + newUri);
328

    
329
            HttpResponse response = UriUtils.getResponse(newUri, null);
330

    
331
            // get the content at the resource
332
            InputStream content = response.getEntity().getContent();
333
            return content;
334

    
335
	   } catch (IOException e) {
336
           logger.error("No content for request: " + request);
337
           throw new RuntimeException(e);
338
       } catch (URISyntaxException e) {
339
			logger.error("Given URL could not be transformed into URI", e);
340
			throw new RuntimeException(e);
341
	   }
342
	}
343

    
344
	private List<Reference> buildPublicationList( InputStream content, ICdmRepository services, IIpniServiceConfigurator iConfig) throws IOException {
345
		IpniServicePublicationConfigurator config = (IpniServicePublicationConfigurator)iConfig;
346

    
347
		List<Reference> result = new ArrayList<>();
348
		BufferedReader reader = new BufferedReader (new InputStreamReader(content));
349

    
350
		String headerLine = reader.readLine();
351
		Map<Integer, String> parameterMap = getParameterMap(headerLine);
352

    
353
		String line = reader.readLine();
354
		while (isNotBlank(line)){
355
			Reference reference = getPublicationFromLine(line, parameterMap, services, config);
356
			result.add(reference);
357
			line = reader.readLine();
358
		}
359

    
360
		return result;
361
	}
362

    
363
	private Reference getPublicationFromLine(String line, Map<Integer, String> parameterMap,
364
	        ICdmRepository repository, IpniServicePublicationConfigurator config) {
365
		//fill value map
366
		String[] splits = line.split("%");
367

    
368
		Map<String, String> valueMap = fillValueMap(parameterMap, splits);
369

    
370
		//create reference object
371
		Reference ref = ReferenceFactory.newGeneric();
372

    
373
		//reference
374
		if (config.isUseAbbreviationAsTitle() == true){
375
			ref.setTitle(valueMap.get(ABBREVIATION));
376
			//TODO handle title as extension
377
		}else{
378
			ref.setTitle(valueMap.get(TITLE));
379
			//TODO handle abbreviation as extension
380
		}
381
		ref.setIsbn(valueMap.get(ISBN));
382
		ref.setIssn(valueMap.get(ISSN));
383
		ref.setEdition(valueMap.get(EDITION));
384
		ref.setPlacePublished(valueMap.get(PLACE));
385

    
386
		String author = valueMap.get(PUBLICATION_AUTHOR_TEAM);
387
		if (isNotBlank(author)){
388
			Team team = Team.NewTitledInstance(author, author);
389
			ref.setAuthorship(team);
390
		}
391

    
392
		//remarks
393
		String remarks = valueMap.get(REMARKS);
394
		if (remarks != null){
395
		    Annotation annotation = Annotation.NewInstance(remarks, AnnotationType.EDITORIAL(), Language.ENGLISH());
396
		    ref.addAnnotation(annotation);
397
		}
398

    
399
		String tl2AuthorString = valueMap.get(TL2_AUTHOR);
400
		if (ref.getAuthorship() == null){
401
			Team tl2Author = Team.NewTitledInstance(tl2AuthorString, null);
402
			ref.setAuthorship(tl2Author);
403
		}else{
404
			//TODO parse name,
405
			ref.getAuthorship().setTitleCache(tl2AuthorString, true);
406
			ref.addAnnotation(Annotation.NewInstance(tl2AuthorString, AnnotationType.EDITORIAL(), Language.ENGLISH()));
407
		}
408

    
409
		//dates
410
		VerbatimTimePeriod date = TimePeriodParser.parseStringVerbatim(valueMap.get(DATE));
411
		ref.setDatePublished(date);
412

    
413
		//source
414
		Reference citation = getIpniCitation(repository);
415
		ref.addSource(OriginalSourceType.Lineage, valueMap.get(ID), "Publication", citation, valueMap.get(VERSION));
416

    
417
/*		TODO
418
		BPH number
419
		Authors role
420
		In publication facade
421
		LC number
422
		Preceded by
423
		TL2 number
424
		TDWG abbreviation
425
	*/
426

    
427
		return ref;
428
	}
429

    
430
	private List<TaxonName> buildNameList( InputStream content, ICdmRepository repository, IIpniServiceConfigurator iConfig) throws IOException {
431
		IpniServiceNamesConfigurator config = (IpniServiceNamesConfigurator)iConfig;
432
		List<TaxonName> result = new ArrayList<>();
433
		BufferedReader reader = new BufferedReader (new InputStreamReader(content));
434

    
435
		String headerLine = reader.readLine();
436
//		System.out.println(headerLine);
437
		Map<Integer, String> parameterMap = getParameterMap(headerLine);
438

    
439
		String line = reader.readLine();
440
		while (isNotBlank(line)){
441
//		    System.out.println(line);
442
		    TaxonName name = (TaxonName)getNameFromLine(line,parameterMap, repository, config);
443
			result.add(name);
444
			line = reader.readLine();
445
		}
446

    
447
		return result;
448
	}
449

    
450
	private static final NonViralNameParserImpl nvnParser = NonViralNameParserImpl.NewInstance();
451

    
452
	private IBotanicalName getNameFromLine(String line, Map<Integer, String> parameterMap, ICdmRepository repository, IpniServiceNamesConfigurator config) {
453
		//Id%Version%Standard form%Default author forename%Default author surname%Taxon groups%Dates%Alternative names
454
		String[] splits = line.split("%");
455

    
456
		Map<String, String> valueMap = fillValueMap(parameterMap, splits);
457

    
458
		IBotanicalName name = TaxonNameFactory.NewBotanicalInstance(null);
459

    
460
		//epithets
461
		name.setGenusOrUninomial(valueMap.get(GENUS));
462
		name.setInfraGenericEpithet(valueMap.get(INFRA_GENUS));
463
		name.setSpecificEpithet(valueMap.get(SPECIES));
464
		name.setInfraSpecificEpithet(valueMap.get(INFRA_SPECIFIC));
465

    
466
		//rank
467
		try {
468
			String rankStr = nomalizeRank(valueMap.get(RANK));
469
			Rank rank = Rank.getRankByLatinNameOrIdInVoc(rankStr, NomenclaturalCode.ICNAFP, true);
470
			name.setRank(rank);
471
		} catch (UnknownCdmTypeException e) {
472
			logger.warn("Rank was unknown");
473
		}
474
        //caches
475
		String pureName = valueMap.get(FULL_NAME_WITHOUT_FAMILY_AND_AUTHORS);
476
        String nameCache = name.getNameCache();
477
		if (!Nz(pureName).equals(nameCache)) {
478
            nvnParser.parseSimpleName(name, valueMap.get(FULL_NAME_WITHOUT_FAMILY_AND_AUTHORS), name.getRank(), true);
479
//            name.setNameCache(valueMap.get(FULL_NAME_WITHOUT_FAMILY_AND_AUTHORS), true);
480
        }
481

    
482
        String authors = "";
483
		//authors
484
		if (valueMap.get(BASIONYM_AUTHOR)!= null){
485
		    authors = valueMap.get(BASIONYM_AUTHOR);
486
//		    name.setBasionymAuthorship(Team.NewTitledInstance(valueMap.get(BASIONYM_AUTHOR), valueMap.get(BASIONYM_AUTHOR)));
487
		}
488
        if (valueMap.get(PUBLISHING_AUTHOR)!= null){
489
            authors += valueMap.get(PUBLISHING_AUTHOR);
490
//            name.setCombinationAuthorship(Team.NewTitledInstance(valueMap.get(PUBLISHING_AUTHOR), valueMap.get(PUBLISHING_AUTHOR)));
491
        }
492
        try {
493
            nvnParser.parseAuthors(name, authors);
494
        } catch (StringNotParsableException e1) {
495
            //
496
        }
497
        if (!Nz(valueMap.get(AUTHORS)).equals(name.getAuthorshipCache())) {
498
            name.setAuthorshipCache(valueMap.get(AUTHORS), true);
499
        }
500
        if ("Y".equals(valueMap.get(HYBRID))){
501
            if (!name.isHybrid()){
502
                //Is there a concrete way to include the hybrid flag info? As it does not say which type of hybrid it seems
503
                //to be best to handle hybrids via parsing. But there might be a better errror handling possible.
504
                logger.warn("Name is flagged as hybrid at IPNI but CDM name has no hybrid flag set: " + name.getTitleCache());
505
            }
506
        }
507

    
508
		//publication
509
		if (valueMap.get(PUBLICATION)!= null || valueMap.get(COLLATION)!= null || valueMap.get(PUBLICATION_YEAR_FULL) != null){
510
		    Reference ref = ReferenceFactory.newGeneric();
511

    
512
		    //TODO probably we can do better parsing here
513
		    String pub = CdmUtils.concat(" ", valueMap.get(PUBLICATION), valueMap.get(COLLATION));
514
		    if (isNotBlank(pub)){
515
    		    String nomRefTitle = pub;
516
    		    String[] split = nomRefTitle.split(":");
517
    		    if (split.length > 1){
518
    		        String detail = split[split.length-1];
519
    		        name.setNomenclaturalMicroReference(detail.trim());
520
    		        nomRefTitle = nomRefTitle.substring(0, nomRefTitle.length() - detail.length() - 1).trim();
521
    		    }
522

    
523
    		    ref.setAbbrevTitle(nomRefTitle);
524
		    }
525

    
526
		    VerbatimTimePeriod datePublished = parsePublicationFullYear(valueMap.get(PUBLICATION_YEAR_FULL));
527
		    ref.setDatePublished(datePublished);
528

    
529
		    name.setNomenclaturalReference(ref);
530
		}
531

    
532
		//name status
533
		NomenclaturalStatusType statusType = null;
534
		String statusString = valueMap.get(NAME_STATUS);
535
		if (isNotBlank(statusString)){
536
			try {
537
				statusType = NomenclaturalStatusType.getNomenclaturalStatusTypeByAbbreviation(statusString, name);
538
				NomenclaturalStatus nomStatus = NomenclaturalStatus.NewInstance(statusType);
539
				name.addStatus(nomStatus);
540
			} catch (UnknownCdmTypeException e) {
541
				logger.warn("Name status not recognized: " + statusString);
542
	            Annotation annotation = Annotation.NewInstance("Name status: " + statusString, AnnotationType.EDITORIAL(), Language.ENGLISH());
543
	            name.addAnnotation(annotation);
544
			}
545
		}
546

    
547
		//remarks
548
		String remarks = valueMap.get(REMARKS);
549
		if (remarks != null){
550
		    Annotation annotation = Annotation.NewInstance(remarks, AnnotationType.EDITORIAL(), Language.ENGLISH());
551
		    name.addAnnotation(annotation);
552
		}
553

    
554
		//basionym
555
		if (config.isDoBasionyms() && valueMap.get(BASIONYM)!= null){
556
		    TaxonName basionym = TaxonNameFactory.NewBotanicalInstance(null);
557
		    basionym.setTitleCache(valueMap.get(BASIONYM), true);
558
		    name.addBasionym(basionym);
559
		}
560

    
561
		//replaced synonym
562
		if (config.isDoBasionyms() && valueMap.get(REPLACED_SYNONYM)!= null){
563
		    TaxonName replacedSynoynm = TaxonNameFactory.NewBotanicalInstance(null);
564
		    replacedSynoynm.setTitleCache(valueMap.get(REPLACED_SYNONYM), true);
565
		    name.addReplacedSynonym(replacedSynoynm, null, null, null, null);
566
		}
567

    
568
		//type information
569
		if (config.isDoType() && valueMap.get(COLLECTION_DATE_AS_TEXT)!= null || valueMap.get(COLLECTION_NUMBER) != null
570
		        || valueMap.get(COLLECTION_DAY1) != null || valueMap.get(COLLECTION_DAY2) != null
571
		        || valueMap.get(COLLECTION_MONTH1) != null || valueMap.get(COLLECTION_MONTH2) != null
572
		        || valueMap.get(COLLECTION_YEAR1) != null || valueMap.get(COLLECTION_YEAR2) != null
573
		        || valueMap.get(COLLECTOR_TEAM_AS_TEXT) != null || valueMap.get(LOCALITY)!= null
574
		        || valueMap.get(LATITUDE_DEGREES) != null || valueMap.get(LATITUDE_MINUTES) != null
575
                || valueMap.get(LATITUDE_SECONDS) != null || valueMap.get(NORTH_OR_SOUTH) != null
576
                || valueMap.get(COLLECTION_YEAR1) != null || valueMap.get(COLLECTION_YEAR2) != null
577
                //TODO TBC
578
		        ){
579
    		DerivedUnitFacade specimen = DerivedUnitFacade.NewInstance(SpecimenOrObservationType.PreservedSpecimen);
580

    
581

    
582
    		//gathering period
583
    		String collectionDateAsText = valueMap.get(COLLECTION_DATE_AS_TEXT);
584
    		TimePeriod gatheringPeriod = TimePeriodParser.parseString(collectionDateAsText);
585

    
586
    		try {
587
    			gatheringPeriod.setStartDay(getIntegerDateValueOrNull(valueMap, COLLECTION_DAY1));
588
    			gatheringPeriod.setStartMonth(getIntegerDateValueOrNull(valueMap, COLLECTION_MONTH1));
589
    			gatheringPeriod.setStartYear(getIntegerDateValueOrNull(valueMap, COLLECTION_YEAR1));
590
    			gatheringPeriod.setEndDay(getIntegerDateValueOrNull(valueMap, COLLECTION_DAY2));
591
    			gatheringPeriod.setEndMonth(getIntegerDateValueOrNull(valueMap, COLLECTION_MONTH2));
592
    			gatheringPeriod.setEndYear(getIntegerDateValueOrNull(valueMap, COLLECTION_YEAR2));
593
    		} catch (IndexOutOfBoundsException e) {
594
    			logger.info("Exception occurred when trying to fill gathering period");
595
    		}
596
    		specimen.setGatheringPeriod(gatheringPeriod);
597

    
598
    		specimen.setFieldNumber(valueMap.get(COLLECTION_NUMBER));
599

    
600
    		//collector team
601
    		String team = valueMap.get(COLLECTOR_TEAM_AS_TEXT);
602
    		if (team != null){
603
    		    Team collectorTeam = Team.NewTitledInstance(team, team);
604
    		    specimen.setCollector(collectorTeam);
605
    		}
606

    
607
    		specimen.setLocality(valueMap.get(LOCALITY));
608

    
609
    		try {
610
    			String latDegrees = CdmUtils.Nz(valueMap.get(LATITUDE_DEGREES));
611
    			String latMinutes = CdmUtils.Nz(valueMap.get(LATITUDE_MINUTES));
612
    			String latSeconds = CdmUtils.Nz(valueMap.get(LATITUDE_SECONDS));
613
    			String direction = CdmUtils.Nz(valueMap.get(NORTH_OR_SOUTH));
614
    			String latitude = latDegrees + "°" + latMinutes + "'" + latSeconds + "\"" + direction;
615

    
616
    			String lonDegrees = CdmUtils.Nz(valueMap.get(LONGITUDE_DEGREES));
617
    			String lonMinutes = CdmUtils.Nz(valueMap.get(LONGITUDE_MINUTES));
618
    			String lonSeconds = CdmUtils.Nz(valueMap.get(LONGITUDE_SECONDS));
619
    			direction = CdmUtils.Nz(valueMap.get(EAST_OR_WEST));
620
    			String longitude = lonDegrees + "°" + lonMinutes + "'" + lonSeconds + "\"" + direction;
621

    
622
    			specimen.setExactLocationByParsing(longitude, latitude, null, null);
623
    		} catch (ParseException e) {
624
    			logger.info("Parsing exception occurred when trying to parse type exact location."  + e.getMessage());
625
    		} catch (Exception e) {
626
    			logger.info("Exception occurred when trying to read type exact location."  + e.getMessage());
627
    		}
628

    
629

    
630
    		//type annotation
631
    		if (valueMap.get(TYPE_REMARKS)!= null){
632
    		    Annotation typeAnnotation = Annotation.NewInstance(valueMap.get(TYPE_REMARKS), AnnotationType.EDITORIAL(), Language.DEFAULT());
633
    		    specimen.addAnnotation(typeAnnotation);
634
    		}
635
		}
636

    
637
		//TODO  Type name
638
		//TODO "Type locations"  , eg. holotype   CAT  ,isotype   CAT  ,isotype   FI
639

    
640
		//TODO Geographic unit as text
641

    
642

    
643
		//source
644
		Reference citation = getIpniCitation(repository);
645
		name.addSource(OriginalSourceType.Lineage, valueMap.get(ID), "Name", citation, valueMap.get(VERSION));
646

    
647

    
648
//		//TODO
649
		//SHORT Family, Infra family, Hybrid genus, Hybrid, Collation, Nomenclatural synonym, Distribution, Citation type
650
/*		EXTENDED
651
 *      Species author,
652
 *       Standardised basionym author flag,
653
 *       Standardised publishing author flag
654
	      Full name
655
	      Full name without family
656
	      Full name without authors
657

    
658
	      Reference
659
	      Standardised publication flag
660
	      Publication year
661
	      publication year note
662
	      Publication year text
663
	      Volume
664
	      Start page
665
	      End page
666
	      Primary pagination
667
	      Secondary pagination
668
	      Reference remarks
669
	      Hybrid parents
670
	      Replaced synonym Author team
671
	      Other links
672
	      Same citation as
673
	      Bibliographic reference
674
	      Bibliographic type info
675

    
676
	      Original taxon name
677
	      Original taxon name author team
678
	      Original replaced synonym
679
	      Original replaced synonym author team
680
	      Original basionym
681
	      Original basionym author team
682
	      Original parent citation taxon name author team
683
	      Original taxon distribution
684
	      Original hybrid parentage
685
	      Original cited type
686
	      Original remarks
687

    
688
		*/
689
		return name;
690
	}
691

    
692
    private String datePatternStr = "([12][0789]\\d{2})\\s\\[([123]?\\d\\s[A-Z][a-z][a-z]\\s[1-2][0789]\\d{2})\\]";
693
    private Pattern datePattern = Pattern.compile(datePatternStr);
694

    
695
	/**
696
     * Parses the full year string as a {@link TimePeriod}
697
     * @param string
698
     * @return
699
     */
700
    private VerbatimTimePeriod parsePublicationFullYear(String fullYearStr) {
701
        VerbatimTimePeriod result = null;
702

    
703
        if (fullYearStr != null){
704
            Matcher matcher = datePattern.matcher(fullYearStr);
705
            if (matcher.matches()){
706
                String yearStr = matcher.group(1);
707
                Integer year = Integer.valueOf(yearStr);
708
                String exactDate = matcher.group(2);
709
                result = TimePeriodParser.parseStringVerbatim(exactDate);
710
                if (!year.equals(result.getStartYear())){
711
                    logger.warn("Year and exact date year do not match");
712
                    result = VerbatimTimePeriod.NewVerbatimInstance(year);
713
                    result.setFreeText(fullYearStr);
714
                }
715
            }else{
716
                result = TimePeriodParser.parseStringVerbatim(fullYearStr);
717
            }
718
        }
719
        return result;
720
    }
721

    
722

    
723
    /**
724
     * Fills the map where the key is the parameter name from the parameterMap and the value is the value from the split.
725
     * @param parameterMap
726
     * @param splits
727
     * @param valueMap
728
     */
729
    private Map<String, String> fillValueMap(Map<Integer, String> parameterMap, String[] splits) {
730
        Map<String, String> result = new HashMap<>();
731
        for (int i = 0; i < splits.length; i++){
732
		    String key = parameterMap.get(i);
733
		    String value = splits[i];
734
		    if (isNotBlank(value)){
735
		        result.put(key, value);
736
		    }
737
		}
738
        return result;
739
    }
740

    
741
	/**
742
	 * @param valueMap
743
	 * @return
744
	 */
745
	private Integer getIntegerDateValueOrNull(Map<String, String> valueMap, String key) {
746
		try {
747
			Integer result = Integer.valueOf(valueMap.get(key));
748
			if (result == 0){
749
				result = null;
750
			}
751
			return result;
752
		} catch (NumberFormatException e) {
753
			if (logger.isDebugEnabled()){
754
				logger.debug("Number Format exception for " + valueMap.get(key));
755
			}
756
			return null;
757
		}
758
	}
759

    
760

    
761
	private List<Person> buildAuthorList(InputStream content, ICdmRepository repository, IIpniServiceConfigurator iConfig) throws IOException {
762
		IpniServiceAuthorConfigurator config = (IpniServiceAuthorConfigurator)iConfig;
763
		List<Person> result = new ArrayList<>();
764
		BufferedReader reader = new BufferedReader (new InputStreamReader(content));
765

    
766
		String headerLine = reader.readLine();
767
		if (headerLine != null){
768
			Map<Integer, String> parameterMap = getParameterMap(headerLine);
769

    
770
			String line = reader.readLine();
771
			while (isNotBlank(line)){
772
				Person author = getAuthorFromLine(line,parameterMap, repository, config);
773
				result.add(author);
774
				line = reader.readLine();
775
			}
776
		}
777

    
778
		return result;
779
	}
780

    
781

    
782

    
783
	private Map<Integer, String> getParameterMap(String headerLine) {
784
		Map<Integer, String> result = new HashMap<Integer, String>();
785
		if ( headerLine != null ){
786
			String[] splits = headerLine.split("%");
787
			for (int i = 0; i < splits.length ; i ++){
788
				result.put(i, splits[i]);
789
			}
790
		}
791
		return result;
792
	}
793

    
794

    
795
	private Person getAuthorFromLine(String line, Map<Integer, String> categoryMap, ICdmRepository repository, IpniServiceAuthorConfigurator config) {
796
		//Id%Version%Standard form%Default author forename%Default author surname%Taxon groups%Dates%Alternative names
797
		String[] splits = line.split("%");
798
		Map<String, String> valueMap = fillValueMap(categoryMap, splits);
799

    
800
		Person person = Person.NewInstance();
801

    
802
		person.setNomenclaturalTitle(valueMap.get(STANDARD_FORM));
803
		person.setGivenName(valueMap.get(DEFAULT_AUTHOR_FORENAME));
804
		person.setFamilyName(valueMap.get(DEFAULT_AUTHOR_SURNAME));
805

    
806
		Reference citation = getIpniCitation(repository);
807

    
808
		//id, version
809
		person.addSource(OriginalSourceType.Lineage, valueMap.get(ID), "Author", citation, valueMap.get(VERSION));
810

    
811
		//dates
812
		TimePeriod lifespan = TimePeriodParser.parseString(valueMap.get(DATES));
813
		person.setLifespan(lifespan);
814

    
815
		//alternative_names
816
		String alternativeNames = valueMap.get(ALTERNATIVE_NAMES);
817
		if (isNotBlank(alternativeNames)){
818
			String[] alternativeNameSplits = alternativeNames.split("%");
819
			for (String alternativeName : alternativeNameSplits){
820
				if (alternativeName.startsWith(">")){
821
					alternativeName = alternativeName.substring(1);
822
				}
823
				Extension.NewInstance(person, alternativeName, ExtensionType.INFORMAL_CATEGORY());
824
			}
825
		}
826

    
827
		//TODO taxonGroups
828

    
829
		return person;
830
	}
831

    
832

    
833
	private Reference getIpniCitation(ICdmRepository appConfig) {
834
		Reference ipniReference;
835
		if (appConfig != null){
836
			ipniReference = appConfig.getReferenceService().find(uuidIpni);
837
			if (ipniReference == null){
838
				ipniReference = getNewIpniReference();
839
				ipniReference.setUuid(uuidIpni);
840
				appConfig.getReferenceService().save(ipniReference);
841
			}
842
		}else{
843
			ipniReference = getNewIpniReference();
844
		}
845
		return ipniReference;
846
	}
847

    
848
	/**
849
	 * @return
850
	 */
851
	private Reference getNewIpniReference() {
852
		Reference ipniReference;
853
		ipniReference = ReferenceFactory.newDatabase();
854
		ipniReference.setTitle("The International Plant Names Index (IPNI)");
855
		return ipniReference;
856
	}
857

    
858

    
859

    
860
	@Override
861
    public List<IBotanicalName> getNamesAdvanced(String family, String genus, String species, String infraFamily,
862
			String infraGenus, String infraSpecies, String authorAbbrev,
863
			String publicationTitle,
864
			Rank rankInRangeToReturn,
865
			IpniServiceNamesConfigurator config,
866
			ICdmRepository services){
867
		IpniRank ipniRank = IpniRank.valueOf(rankInRangeToReturn);
868
		return getNamesAdvanced(family, genus, species, infraFamily, infraGenus, infraSpecies, authorAbbrev, publicationTitle, ipniRank, config, services);
869
	}
870

    
871
	@Override
872
    public List<IBotanicalName> getNamesAdvanced(String family, String genus, String species, String infraFamily,
873
			String infraGenus, String infraSpecies, String authorAbbrev,
874
			String publicationTitle,
875
			IpniRank rankToReturn,
876
			IpniServiceNamesConfigurator config,
877
			ICdmRepository services) {
878

    
879
//		find_rankToReturn=all&output_format=normal&find_sortByFamily=on&find_sortByFamily=off&query_type=by_query&back_page=plantsearch
880

    
881
		//config
882
		if (config == null){
883
			config = new IpniServiceNamesConfigurator();
884
		}
885

    
886

    
887
		family = normalizeParameter(family);
888
		genus = normalizeParameter(genus);
889
		species = normalizeParameter(species);
890
		infraFamily = normalizeParameter(infraFamily);
891
		infraGenus = normalizeParameter(infraGenus);
892
		infraSpecies = normalizeParameter(infraSpecies);
893
		authorAbbrev = normalizeParameter(authorAbbrev);
894

    
895
		publicationTitle = normalizeParameter(publicationTitle);
896

    
897
		DelimitedFormat format = config.getFormat();
898

    
899
		String request =
900
				"find_family=" + family +
901
				"&find_genus=" + genus +
902
				"&find_species=" + species +
903
				"&find_infrafamily=" + infraFamily +
904
				"&find_infragenus=" + infraGenus +
905
				"&find_infraspecies=" + infraSpecies +
906
				"&find_authorAbbrev=" + authorAbbrev +
907
				getBooleanParameter("&find_includePublicationAuthors=", config.isIncludePublicationAuthors(), "on", "off") +
908
				getBooleanParameter("&find_includeBasionymAuthors=", config.isIncludeBasionymAuthors(), "on", "off") +
909
				getBooleanParameter("&find_isAPNIRecord=", config.isDoApni(), "on", "false") +
910
				getBooleanParameter("&find_isGCIRecord=", config.isDoGci(), "on", "false") +
911
				getBooleanParameter("&find_isIKRecord=", config.isDoIk(), "on", "false") +
912
				getBooleanParameter("&find_sortByFamily=", config.isSortByFamily(), "on", "off") +
913
				(rankToReturn == null? "all" : rankToReturn.strRank)+
914
				"&find_publicationTitle=" + publicationTitle +
915
				"&output_format=" + format.parameter;
916

    
917
		System.out.println(request);
918
		return (List)queryService(request, services, getServiceUrl(IIpniService.ADVANCED_NAME_SERVICE_URL), config, ServiceType.NAME);
919
	}
920

    
921

    
922

    
923
    private String getBooleanParameter(String urlParamString, Boolean booleanParameter, String trueString, String falseString) {
924
		String result;
925
		if (booleanParameter == null){
926
			result = getBooleanParameter(urlParamString, true, trueString, falseString) + getBooleanParameter(urlParamString, false, trueString, falseString);
927
		}else if (booleanParameter == true){
928
			result = urlParamString + trueString;
929
		}else {
930
			result = urlParamString + falseString;
931
		}
932
		return result;
933
	}
934

    
935

    
936
	@Override
937
    public List<IBotanicalName> getNamesSimple(String wholeName, ICdmRepository repository,
938
            IpniServiceNamesConfigurator config){
939
		if (config == null){
940
			config = new IpniServiceNamesConfigurator();
941
		}
942

    
943
//		query_type=by_query&back_page=query_ipni.html
944

    
945
		wholeName = normalizeParameter(wholeName);
946

    
947
		DelimitedFormat format = config.getFormat();
948

    
949
		String request = "find_wholeName=" + wholeName +
950
						"&output_format=" + format.parameter;
951

    
952
		return (List)queryService(request, repository, getServiceUrl(IIpniService.SIMPLE_NAME_SERVICE_URL), config, ServiceType.NAME);
953
	}
954

    
955
	@Override
956
    public List<Reference> getPublications(String title, String abbreviation, ICdmRepository services, IpniServicePublicationConfigurator config){
957
//		http://www.uk.ipni.org/ipni/advPublicationSearch.do?find_title=Spe*plant*&find_abbreviation=&output_format=normal&query_type=by_query&back_page=publicationsearch
958
//		http://www.uk.ipni.org/ipni/advPublicationSearch.do?find_title=*Hortus+Britannicus*&find_abbreviation=&output_format=delimited-classic&output_format=delimited
959

    
960
		if (config == null){
961
			config = new IpniServicePublicationConfigurator();
962
		}
963

    
964
		title = normalizeParameter(title);
965
		abbreviation = normalizeParameter(abbreviation);
966

    
967
		String request = "find_title=" + title +
968
						"&find_abbreviation=" + abbreviation +
969
						"&output_format=" + DelimitedFormat.CLASSIC.parameter;
970

    
971
		List<Reference> result = (List)queryService(request, services, getServiceUrl(IIpniService.PUBLICATION_SERVICE_URL), config, ServiceType.PUBLICATION);
972
		return result;
973
	}
974

    
975

    
976
	/**
977
	 * The service url
978
	 *
979
	 * @return the serviceUrl
980
	 */
981
	@Override
982
    public URL getServiceUrl(String url) {
983
		URL serviceUrl;
984
		try {
985
			serviceUrl = new URL(url);
986
		} catch (MalformedURLException e) {
987
			throw new RuntimeException("This should not happen", e);
988
		}
989
		return serviceUrl;
990
	}
991

    
992

    
993
	@Override
994
	public InputStream getNamesById(String id) {
995
		String request = "id="+id + "&output_format=lsid-metadata";
996
		return queryServiceForID(request, getServiceUrl(IIpniService.ID_NAMESEARCH_SERVICE_URL));
997
	}
998

    
999
	@Override
1000
	public InputStream getPublicationsById(String id) {
1001
		String request = "id="+id ;
1002
		return queryServiceForID(request, getServiceUrl(IIpniService.ID_PUBLICATION_SERVICE_URL));
1003
	}
1004

    
1005

    
1006
    /**
1007
     * @param parameter
1008
     */
1009
    private String normalizeParameter(String parameter) {
1010
        String result = CdmUtils.Nz(parameter).replace(" ", "+");
1011
        return result;
1012
    }
1013

    
1014
    private String nomalizeRank(String string) {
1015
        if (string == null){
1016
            return null;
1017
        }
1018
        String result = string.replace("spec.", "sp.");
1019
        return result;
1020
    }
1021

    
1022
    /**
1023
     * @return
1024
     */
1025
    private DelimitedFormat getDefaultFormat() {
1026
        return DelimitedFormat.SHORT;
1027
    }
1028

    
1029
    private boolean isNotBlank(String line) {
1030
        return StringUtils.isNotBlank(line);
1031
    }
1032

    
1033
    @NotNull
1034
    private String Nz(String string) {
1035
        return CdmUtils.Nz(string);
1036
    }
1037

    
1038

    
1039
}
(3-3/7)