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.URI;
18
import java.net.URISyntaxException;
19
import java.net.URL;
20
import java.text.ParseException;
21
import java.util.ArrayList;
22
import java.util.HashMap;
23
import java.util.List;
24
import java.util.Map;
25
import java.util.regex.Matcher;
26
import java.util.regex.Pattern;
27

    
28
import javax.validation.constraints.NotNull;
29

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

    
35
import eu.etaxonomy.cdm.api.application.ICdmRepository;
36
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
37
import eu.etaxonomy.cdm.common.CdmUtils;
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 = newUrl.toURI();
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

    
327
            URI newUri = newUrl.toURI();
328

    
329
            logger.info("Firing request for URI: " + newUri);
330

    
331
            HttpResponse response = UriUtils.getResponse(newUri, null);
332

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

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

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

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

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

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

    
362
		return result;
363
	}
364

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

    
370
		Map<String, String> valueMap = fillValueMap(parameterMap, splits);
371

    
372
		//create reference object
373
		Reference ref = ReferenceFactory.newGeneric();
374

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

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

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

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

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

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

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

    
429
		return ref;
430
	}
431

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

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

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

    
449
		return result;
450
	}
451

    
452
	private static final NonViralNameParserImpl nvnParser = NonViralNameParserImpl.NewInstance();
453

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

    
458
		Map<String, String> valueMap = fillValueMap(parameterMap, splits);
459

    
460
		IBotanicalName name = TaxonNameFactory.NewBotanicalInstance(null);
461

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

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

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

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

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

    
525
    		    ref.setAbbrevTitle(nomRefTitle);
526
		    }
527

    
528
		    VerbatimTimePeriod datePublished = parsePublicationFullYear(valueMap.get(PUBLICATION_YEAR_FULL));
529
		    ref.setDatePublished(datePublished);
530

    
531
		    name.setNomenclaturalReference(ref);
532
		}
533

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

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

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

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

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

    
583

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

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

    
600
    		specimen.setFieldNumber(valueMap.get(COLLECTION_NUMBER));
601

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

    
609
    		specimen.setLocality(valueMap.get(LOCALITY));
610

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

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

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

    
631

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

    
639
		//TODO  Type name
640
		//TODO "Type locations"  , eg. holotype   CAT  ,isotype   CAT  ,isotype   FI
641

    
642
		//TODO Geographic unit as text
643

    
644

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

    
649

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

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

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

    
690
		*/
691
		return name;
692
	}
693

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

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

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

    
724

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

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

    
762

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

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

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

    
780
		return result;
781
	}
782

    
783

    
784

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

    
796

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

    
802
		Person person = Person.NewInstance();
803

    
804
		person.setNomenclaturalTitle(valueMap.get(STANDARD_FORM));
805
		person.setGivenName(valueMap.get(DEFAULT_AUTHOR_FORENAME));
806
		person.setFamilyName(valueMap.get(DEFAULT_AUTHOR_SURNAME));
807

    
808
		Reference citation = getIpniCitation(repository);
809

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

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

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

    
829
		//TODO taxonGroups
830

    
831
		return person;
832
	}
833

    
834

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

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

    
860

    
861

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

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

    
881
//		find_rankToReturn=all&output_format=normal&find_sortByFamily=on&find_sortByFamily=off&query_type=by_query&back_page=plantsearch
882

    
883
		//config
884
		if (config == null){
885
			config = new IpniServiceNamesConfigurator();
886
		}
887

    
888

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

    
897
		publicationTitle = normalizeParameter(publicationTitle);
898

    
899
		DelimitedFormat format = config.getFormat();
900

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

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

    
923

    
924

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

    
937

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

    
945
//		query_type=by_query&back_page=query_ipni.html
946

    
947
		wholeName = normalizeParameter(wholeName);
948

    
949
		DelimitedFormat format = config.getFormat();
950

    
951
		String request = "find_wholeName=" + wholeName +
952
						"&output_format=" + format.parameter;
953

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

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

    
962
		if (config == null){
963
			config = new IpniServicePublicationConfigurator();
964
		}
965

    
966
		title = normalizeParameter(title);
967
		abbreviation = normalizeParameter(abbreviation);
968

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

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

    
977

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

    
994

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

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

    
1007

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

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

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

    
1031
    private boolean isNotBlank(String line) {
1032
        return StringUtils.isNotBlank(line);
1033
    }
1034

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

    
1040

    
1041
}
(3-3/7)