Project

General

Profile

Download (21.6 KB) Statistics
| Branch: | Tag: | Revision:
1
package org.cybertaxonomy.utis.checklist;
2

    
3
import java.io.PrintStream;
4
import java.net.URI;
5
import java.util.ArrayList;
6
import java.util.EnumSet;
7
import java.util.Iterator;
8
import java.util.List;
9

    
10
import org.apache.lucene.queryParser.QueryParser;
11
import org.cybertaxonomy.utis.client.ServiceProviderInfo;
12
import org.cybertaxonomy.utis.query.TinkerPopClient;
13
import org.cybertaxonomy.utis.store.Neo4jStore;
14
import org.cybertaxonomy.utis.store.Neo4jStoreUpdater;
15
import org.cybertaxonomy.utis.tnr.msg.Classification;
16
import org.cybertaxonomy.utis.tnr.msg.NameType;
17
import org.cybertaxonomy.utis.tnr.msg.Query;
18
import org.cybertaxonomy.utis.tnr.msg.Query.Request;
19
import org.cybertaxonomy.utis.tnr.msg.Response;
20
import org.cybertaxonomy.utis.tnr.msg.Source;
21
import org.cybertaxonomy.utis.tnr.msg.Synonym;
22
import org.cybertaxonomy.utis.tnr.msg.Taxon;
23
import org.cybertaxonomy.utis.tnr.msg.TaxonBase;
24
import org.cybertaxonomy.utis.tnr.msg.TaxonName;
25
import org.cybertaxonomy.utis.tnr.msg.TnrMsg;
26
import org.cybertaxonomy.utis.utils.IdentifierUtils;
27
import org.cybertaxonomy.utis.utils.Profiler;
28
import org.cybertaxonomy.utis.utils.TnrMsgUtils;
29
import org.neo4j.graphdb.Relationship;
30

    
31
import com.tinkerpop.blueprints.Graph;
32
import com.tinkerpop.blueprints.Vertex;
33
import com.tinkerpop.blueprints.impls.neo4j2.Neo4j2Vertex;
34
import com.tinkerpop.blueprints.oupls.sail.GraphSail;
35
import com.tinkerpop.gremlin.java.GremlinPipeline;
36
import com.tinkerpop.pipes.util.FastNoSuchElementException;
37
import com.tinkerpop.pipes.util.structures.Table;
38

    
39
public class EEA_BDC_Client extends AggregateChecklistClient<TinkerPopClient> {
40

    
41
    /**
42
     *
43
     */
44
    public static final String ID = "eea_bdc";
45
    public static final String LABEL = "European Environment Agency (EEA) Biodiversity data centre (BDC)";
46
    public static final String DOC_URL = "http://semantic.eea.europa.eu/documentation";
47
    public static final String COPYRIGHT_URL = "http://www.eea.europa.eu/legal/eea-data-policy";
48

    
49
    private static final String SPECIES_RDF_FILE_URL = "http://localhost/download/species.rdf.gz"; // http://eunis.eea.europa.eu/rdf/species.rdf.gz
50
    private static final String TAXONOMY_RDF_FILE_URL = "http://localhost/download/taxonomy.rdf.gz"; // http://eunis.eea.europa.eu/rdf/taxonomy.rdf.gz
51
    private static final String LEGALREFS_RDF_FILE_URL = "http://localhost/download/legalrefs.rdf.gz"; // http://eunis.eea.europa.eu/rdf/legalrefs.rdf.gz
52
    private static final String REFERENCES_RDF_FILE_URL = "http://localhost/download/references.rdf.gz"; // http://eunis.eea.europa.eu/rdf/references.rdf.gz
53

    
54
    /**
55
     * check for updates once a day
56
     */
57
    private static final int CHECK_UPDATE_MINUTES = 1; //60 * 24;
58

    
59
    public static final EnumSet<SearchMode> SEARCH_MODES = EnumSet.of(
60
            SearchMode.scientificNameExact,
61
            SearchMode.scientificNameLike,
62
            SearchMode.vernacularNameExact,
63
            SearchMode.vernacularNameLike,
64
            SearchMode.findByIdentifier);
65

    
66
    public static enum RdfSchema {
67

    
68
        /*
69
         *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
70
    xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
71
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
72
    xmlns:dcterms="http://purl.org/dc/terms/"
73
    xmlns:dc="http://purl.org/dc/elements/1.1/"
74
    xmlns:dwc="http://rs.tdwg.org/dwc/terms/"
75
    xmlns:owl="http://www.w3.org/2002/07/owl#"
76
    xmlns="http://eunis.eea.europa.eu/rdf/species-schema.rdf#"
77
    xmlns:sioc="http://rdfs.org/sioc/ns#"
78
    xmlns:skos="http://www.w3.org/2004/02/skos/core#"
79
    xmlns:bibo="http://purl.org/ontology/bibo/"
80
    xmlns:cc="http://creativecommons.org/ns#"
81
    xmlns:foaf="http://xmlns.com/foaf/0.1/"
82
         */
83
        EUNIS_SPECIES("es","http://eunis.eea.europa.eu/rdf/species-schema.rdf#"),
84
        EUNIS_TAXONOMY("et", "http://eunis.eea.europa.eu/rdf/taxonomies-schema.rdf#"),
85
        DWC("dwc", "http://rs.tdwg.org/dwc/terms/"),
86
        RDF("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
87
        RDFS("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
88
        SKOS_CORE("scos_core", "http://www.w3.org/2004/02/skos/core#"),
89
        DC("dc", "http://purl.org/dc/terms/source"),
90
        DCTERMS("dcterms", "http://purl.org/dc/terms/");
91

    
92
        private String schemaUri;
93
        private String abbreviation;
94
        RdfSchema(String abbreviation, String schemaUri) {
95
            this.abbreviation = abbreviation;
96
            this.schemaUri = schemaUri;
97
        }
98

    
99
        public String schemaUri() {
100

    
101
            return schemaUri;
102
        }
103

    
104
        public String abbreviation() {
105

    
106
            return abbreviation;
107
        }
108

    
109
        public String property(String name) {
110
            return schemaUri + name;
111
        }
112

    
113
    }
114

    
115
    public enum SubCheckListId {
116

    
117
        eunis, natura_2000;
118
    }
119

    
120
    private enum RankLevel{
121

    
122
        Kingdom, Phylum, Clazz, Order, Family, Genus;
123
    }
124

    
125
    public EEA_BDC_Client() {
126

    
127
        super();
128
    }
129

    
130
    public EEA_BDC_Client(String checklistInfoJson) throws DRFChecklistException {
131

    
132
        super(checklistInfoJson);
133
    }
134

    
135
    @Override
136
    public void initQueryClient() {
137

    
138
            Neo4jStore neo4jStore;
139
            try {
140
                neo4jStore = new Neo4jStore();
141
                Neo4jStoreUpdater updater = new Neo4jStoreUpdater(neo4jStore, SPECIES_RDF_FILE_URL);
142
                updater.addResources(SPECIES_RDF_FILE_URL, TAXONOMY_RDF_FILE_URL, LEGALREFS_RDF_FILE_URL, REFERENCES_RDF_FILE_URL);
143
                updater.watch(CHECK_UPDATE_MINUTES);
144
            } catch (Exception e1) {
145
                throw new RuntimeException("Creation of Neo4jStore failed",  e1);
146
            }
147
            queryClient = new TinkerPopClient(neo4jStore);
148
    }
149

    
150
    @Override
151
    public ServiceProviderInfo buildServiceProviderInfo() {
152

    
153
        ServiceProviderInfo checklistInfo = new ServiceProviderInfo(ID, LABEL, DOC_URL, COPYRIGHT_URL, getSearchModes());
154
        checklistInfo.addSubChecklist(new ServiceProviderInfo(SubCheckListId.eunis.name(), "EUNIS",
155
                "http://www.eea.europa.eu/themes/biodiversity/eunis/eunis-db#tab-metadata",
156
                "http://www.eea.europa.eu/legal/copyright", SEARCH_MODES));
157
        return checklistInfo;
158
    }
159

    
160

    
161
    /**
162
     * @param queryString
163
     * @throws DRFChecklistException
164
     */
165
    private void addPrexfixes(StringBuilder queryString) throws DRFChecklistException {
166

    
167
        for(RdfSchema schema : RdfSchema.values()) {
168
            queryString.append(String.format("PREFIX %s: <%s>\n", schema.abbreviation(), schema.schemaUri()));
169
        }
170
    }
171

    
172
    /**
173
     * @param checklistInfo
174
     * @return
175
     * @throws DRFChecklistException
176
     */
177
    private StringBuilder prepareQueryString() throws DRFChecklistException {
178

    
179
        StringBuilder queryString = new StringBuilder();
180
        addPrexfixes(queryString);
181
        return queryString;
182
    }
183

    
184
    private Taxon createTaxon(Vertex v) {
185

    
186
        Taxon taxon = new Taxon();
187

    
188
        TaxonName taxonName = createTaxonName(v);
189

    
190
        // Taxon
191
        taxon.setTaxonName(taxonName);
192
        taxon.setIdentifier(v.getId().toString());
193
        taxon.setAccordingTo(queryClient.relatedVertexValue(v, RdfSchema.DWC, "nameAccordingToID"));
194
        URI typeUri = queryClient.vertexURI(v, RdfSchema.RDF, "type");
195
        taxon.setTaxonomicStatus(typeUri.getFragment());
196

    
197
        createSources(v, taxon);
198

    
199
        // classification
200
        Classification c = null;
201
        Vertex parentV= null;
202
        try {
203
            parentV = queryClient.relatedVertex(v, RdfSchema.EUNIS_SPECIES, "taxonomy");
204
        } catch (Exception e) {
205
            logger.error("No taxonomy information for " + v.toString());
206
        }
207

    
208
        while (parentV != null) {
209
            logger.debug("parent taxon: " + parentV.toString());
210
            String level = queryClient.relatedVertexValue(parentV, RdfSchema.EUNIS_TAXONOMY, "level");
211
            String parentTaxonName = queryClient.relatedVertexValue(parentV, RdfSchema.EUNIS_TAXONOMY, "name");
212

    
213
            RankLevel rankLevel = null;
214
            try {
215
                rankLevel = RankLevel.valueOf(level);
216
            } catch (Exception e) {
217
                // IGNORE
218
            }
219
            if(rankLevel != null) {
220
                if(c == null) {
221
                 c = new Classification();
222
                }
223
                switch(rankLevel) {
224
                case Clazz:
225
                    c.setClazz(parentTaxonName);
226
                    break;
227
                case Family:
228
                    c.setFamily(parentTaxonName);
229
                    break;
230
                case Genus:
231
                    c.setGenus(parentTaxonName);
232
                    break;
233
                case Kingdom:
234
                    c.setKingdom(parentTaxonName);
235
                    break;
236
                case Order:
237
                    c.setOrder(parentTaxonName);
238
                    break;
239
                case Phylum:
240
                    c.setPhylum(parentTaxonName);
241
                    break;
242
                default:
243
                    break;
244
                }
245
            }
246
            Vertex lastParentV = parentV;
247
            parentV = queryClient.relatedVertex(parentV, RdfSchema.EUNIS_TAXONOMY, "parent");
248
            if(lastParentV.equals(parentV)) {
249
                // avoid endless looping when data is not correct
250
                break;
251
            }
252
        }
253
        if(c != null) {
254
            taxon.setClassification(c);
255
        }
256
        return taxon;
257
    }
258

    
259
    /**
260
     * @param model
261
     * @param taxonR
262
     * @param taxonBase
263
     */
264
    private void createSources(Vertex v, TaxonBase taxonBase) {
265

    
266
        // Sources are source references, re there others like data bases?
267

    
268
        GremlinPipeline<Graph, Vertex> taxonPipe = new GremlinPipeline<Graph, Vertex>(v);
269

    
270
        try {
271
            List<Vertex> titleVs = taxonPipe
272
                    .outE(RdfSchema.EUNIS_SPECIES.property("hasLegalReference")).inV()
273
                    .outE(RdfSchema.DCTERMS.property("source")).inV().dedup()
274
                    .outE(RdfSchema.DCTERMS.property("title")).inV()
275
                    .toList();
276
            for(Vertex tv : titleVs) {
277
                Source source = new Source();
278
                logger.error(tv.toString());
279
                source.setName(tv.getProperty(GraphSail.VALUE).toString());
280
                taxonBase.getSources().add(source);
281
            }
282
        } catch (FastNoSuchElementException e) {
283
            logger.debug("No sources found");
284
        }
285
    }
286

    
287
    /**
288
     * @param taxonR
289
     * @return
290
     */
291
    private TaxonName createTaxonName(Vertex v) {
292

    
293
        TaxonName taxonName = new TaxonName();
294
        // TaxonName
295
        taxonName.setFullName(queryClient.relatedVertexValue(v, RdfSchema.RDFS, "label"));
296
        taxonName.setCanonicalName(queryClient.relatedVertexValue(v, RdfSchema.EUNIS_SPECIES, "binomialName"));
297
        taxonName.setRank(queryClient.relatedVertexValue(v, RdfSchema.EUNIS_SPECIES, "taxonomicRank"));
298
        return taxonName;
299
    }
300

    
301

    
302
    private void createSynonyms(Vertex taxonV, Response tnrResponse) {
303

    
304

    
305
        GremlinPipeline<Graph, Vertex> taxonPipe = new GremlinPipeline<Graph, Vertex>(taxonV);
306

    
307
        try {
308
            List<Vertex> synonymVs = taxonPipe
309
                    .inE(RdfSchema.EUNIS_SPECIES.property("eunisPrimaryName")).outV().dedup()
310
                    .toList();
311
            for(Vertex synonymV : synonymVs) {
312
                String typeUri = queryClient.relatedVertexValue(synonymV, RdfSchema.RDF, "type");
313
                String status = null;
314
                try {
315
                    status = URI.create(typeUri).getFragment();
316
                } catch (Exception e) {
317

    
318
                }
319

    
320
                if (status != null && status.equals("SpeciesSynonym")) {
321

    
322
                    Synonym synonym = new Synonym();
323

    
324
                    TaxonName taxonName = createTaxonName(synonymV);
325
                    synonym.setTaxonomicStatus(status);
326
                    synonym.setTaxonName(taxonName);
327
                    synonym.setAccordingTo(queryClient.relatedVertexValue(synonymV, RdfSchema.DWC, "nameAccordingToID"));
328

    
329
                    createSources(synonymV, synonym);
330

    
331
                    tnrResponse.getSynonym().add(synonym);
332
                }
333
            }
334
        } catch (FastNoSuchElementException e) {
335
            logger.debug("No sources found");
336
        }
337

    
338
    }
339

    
340
    @Override
341
    public void resolveScientificNamesExact(TnrMsg tnrMsg) throws DRFChecklistException {
342

    
343
        for (ServiceProviderInfo checklistInfo : getServiceProviderInfo().getSubChecklists()) {
344

    
345
            // FIXME query specific subchecklist
346

    
347
            // selecting one request as representative, only
348
            // the search mode and addSynonmy flag are important
349
            // for the further usage of the request object
350
            Query query = singleQueryFrom(tnrMsg);
351

    
352
            String queryString = query.getRequest().getQueryString();
353
            logger.debug("original queryString: "+ queryString);
354
            queryString = QueryParser.escape(queryString);
355
            queryString = queryString.replace(" ", "\\ ");
356
            if(query.getRequest().getSearchMode().equals(SearchMode.scientificNameLike.name())) {
357
                queryString += "*";
358
            }
359
            logger.debug("prepared queryString: "+ queryString);
360

    
361
            GremlinPipeline<Graph, Vertex> pipe = null;
362

    
363
            Profiler profiler = Profiler.newCpuProfiler(false);
364

    
365
            logger.debug("Neo4jINDEX");
366

    
367
            ArrayList<Vertex> hitVs = queryClient.vertexIndexQuery("value:" + queryString);
368
            pipe = new GremlinPipeline<Graph, Vertex>(hitVs);
369

    
370
            List<Vertex> vertices = new ArrayList<Vertex>();
371
            pipe.in(RdfSchema.EUNIS_SPECIES.property("binomialName")).fill(vertices);
372

    
373
            updateQueriesWithResponse(vertices, null, null, checklistInfo, query);
374
            profiler.end(System.err);
375
        }
376
    }
377

    
378
    @Override
379
    public void resolveScientificNamesLike(TnrMsg tnrMsg) throws DRFChecklistException {
380
        // delegate to resolveScientificNamesExact,
381
        resolveScientificNamesExact(tnrMsg);
382

    
383
    }
384

    
385
    @Override
386
    public void resolveVernacularNamesExact(TnrMsg tnrMsg) throws DRFChecklistException {
387
        List<Query> queryList = tnrMsg.getQuery();
388

    
389
        for (ServiceProviderInfo checklistInfo : getServiceProviderInfo().getSubChecklists()) {
390

    
391
            // FIXME query specific subchecklist
392

    
393
            // selecting one request as representative, only
394
            // the search mode and addSynonmy flag are important
395
            // for the further usage of the request object
396
            Query query = singleQueryFrom(tnrMsg);
397

    
398
            String queryString = query.getRequest().getQueryString();
399
            logger.debug("original queryString: "+ queryString);
400
            queryString = QueryParser.escape(queryString);
401
            queryString = queryString.replace(" ", "\\ ");
402
            if(query.getRequest().getSearchMode().equals(SearchMode.vernacularNameLike.name())) {
403
                queryString = "*" + queryString + "*";
404
            }
405

    
406
            logger.debug("prepared queryString: "+ queryString);
407

    
408
            GremlinPipeline<Graph, Vertex> pipe = null;
409

    
410
            Profiler profiler = Profiler.newCpuProfiler(false);
411

    
412
            // by using the Neo4j index directly it is possible to
413
            // take full advantage of the underlying Lucene search engine
414
            ArrayList<Vertex> hitVs = queryClient.vertexIndexQuery("value:" + queryString);
415

    
416
//            List<String> matchingNames = new ArrayList<String>(hitVs.size());
417
//            for(Vertex v : hitVs) {
418
//                String matchValue = v.getProperty(GraphSail.VALUE).toString();
419
//                matchingNames.add(matchValue);
420
//                logger.debug("matchingName  " + matchValue);
421
//            }
422

    
423
            List<Vertex> vertices = new ArrayList<Vertex>();
424
            pipe = new GremlinPipeline<Graph, Vertex>(hitVs);
425
            Table table = new Table();
426
            pipe.as("match").in(RdfSchema.DWC.property("vernacularName")).as("taxon").table(table).iterate();
427

    
428
            updateQueriesWithResponse(
429
                    table.getColumn("taxon"), table.getColumn("match"),
430
                    NameType.VERNACULAR_NAME, checklistInfo, query);
431
            profiler.end(System.err);
432
        }
433
    }
434

    
435
    @Override
436
    public void resolveVernacularNamesLike(TnrMsg tnrMsg) throws DRFChecklistException {
437
        resolveVernacularNamesExact(tnrMsg);
438
    }
439

    
440
    @Override
441
    public void findByIdentifier(TnrMsg tnrMsg) throws DRFChecklistException {
442

    
443
        for (ServiceProviderInfo checklistInfo : getServiceProviderInfo().getSubChecklists()) {
444

    
445
            // FIXME query specific subchecklist
446
            Query query = singleQueryFrom(tnrMsg);
447
            String queryString = query.getRequest().getQueryString();
448

    
449
            // by using the Neo4j index directly it is possible to
450
            // take full advantage of the underlying Lucene search engine
451
            queryString = QueryParser.escape(queryString);
452
            ArrayList<Vertex> hitVs = queryClient.vertexIndexQuery("value:" + queryString);
453
            if(hitVs.size() > 0) {
454
                Response response = tnrResponseFromResource(hitVs.get(0), query.getRequest(), null, null);
455
                query.getResponse().add(response);
456
            } else if(hitVs.size() > 1) {
457
                throw new DRFChecklistException("More than one node with the id '" + queryString + "' found");
458
            }
459
        }
460
    }
461

    
462
    private void updateQueriesWithResponse(List<Vertex> taxonNodes, List<Vertex> matchNodes, NameType matchType, ServiceProviderInfo ci, Query query){
463

    
464
        if (taxonNodes == null) {
465
            return;
466
        }
467

    
468
        logger.debug("matching taxon nodes:");
469
        int i = -1;
470
        for (Vertex v : taxonNodes) {
471
            i++;
472
            logger.debug("  " + v.toString());
473
            printPropertyKeys(v, System.err);
474
            if(v.getProperty("kind").equals("url")) {
475
                logger.error("vertex of type 'url' expected, but was " + v.getProperty("type").equals("url"));
476
                continue;
477
            }
478
            Vertex matchNode = null;
479
            if(matchNodes != null) {
480
                matchNode = matchNodes.get(i);
481
            }
482
            Response tnrResponse = tnrResponseFromResource(v, query.getRequest(), matchNode, matchType);
483
            if(tnrResponse != null) {
484
                query.getResponse().add(tnrResponse);
485
            }
486
        }
487
    }
488

    
489
    /**
490
     * @param model
491
     * @param taxonR
492
     * @param request
493
     * @param matchType
494
     * @param matchNode
495
     * @return
496
     */
497
    private Response tnrResponseFromResource(Vertex taxonV, Request request, Vertex matchNode, NameType matchType) {
498

    
499
        Response tnrResponse = TnrMsgUtils.tnrResponseFor(getServiceProviderInfo());
500

    
501
        GremlinPipeline<Graph, Vertex> pipe = new GremlinPipeline<Graph, Vertex>(taxonV);
502

    
503
        String validName = queryClient.relatedVertexValue(taxonV, RdfSchema.EUNIS_SPECIES, "validName");
504

    
505
        boolean isAccepted = validName != null && validName.equals("true");
506

    
507
        logger.debug("processing " + (isAccepted ? "accepted taxon" : "synonym or other")  + " " + taxonV.getId());
508

    
509
        //
510
        if(matchNode != null) {
511
            String matchingName = matchNode.getProperty(GraphSail.VALUE).toString();
512
            tnrResponse.setMatchingNameString(matchingName);
513
            tnrResponse.setMatchingNameType(matchType);
514
        }
515

    
516
        // case when accepted name
517
        if(isAccepted) {
518
            Taxon taxon = createTaxon(taxonV);
519
            tnrResponse.setTaxon(taxon);
520
            if(matchNode == null) {
521
                tnrResponse.setMatchingNameType(NameType.TAXON);
522
                String matchingName = taxon.getTaxonName().getCanonicalName();
523
                tnrResponse.setMatchingNameString(matchingName);
524
            }
525

    
526
        }
527
        else {
528
            // case when synonym
529
            Vertex synonymV = taxonV;
530
            taxonV = null;
531
            try {
532
                taxonV = queryClient.relatedVertex(synonymV, RdfSchema.EUNIS_SPECIES, "eunisPrimaryName");
533
            } catch(Exception e) {
534
                logger.error("No accepted taxon found for " + synonymV.toString() + " (" + synonymV.getProperty(GraphSail.VALUE) + ")");
535
            }
536

    
537
            if(taxonV != null) {
538
                Taxon taxon = createTaxon(taxonV);
539
                tnrResponse.setTaxon(taxon);
540
            } else {
541
            }
542
            if(matchNode == null) {
543
                tnrResponse.setMatchingNameType(NameType.SYNONYM);
544
                String matchingName = queryClient.relatedVertexValue(synonymV, RdfSchema.EUNIS_SPECIES, "binomialName");
545
                tnrResponse.setMatchingNameString(matchingName);
546
            }
547
        }
548

    
549
        if(request.isAddSynonymy()) {
550
            createSynonyms(taxonV, tnrResponse);
551
        }
552

    
553
        logger.debug("processing " + (isAccepted ? "accepted taxon" : "synonym or other")  + " " + taxonV.getId() + " DONE");
554
        return tnrResponse;
555
    }
556

    
557
    /**
558
     * @param vertex
559
     */
560
    private void printEdges(Neo4j2Vertex vertex) {
561
        Iterable<Relationship> rels = vertex.getRawVertex().getRelationships();
562
        Iterator<Relationship> iterator = rels.iterator();
563
        if(iterator.hasNext()) {
564
            Relationship rel = iterator.next();
565
            System.err.println(rel.toString() + ": " + rel.getStartNode().toString() + "-[" +  rel.getType() + "]-" + rel.getEndNode().toString());
566
        }
567
    }
568

    
569
    private void printPropertyKeys(Vertex v, PrintStream ps) {
570
        StringBuilder out = new StringBuilder();
571
        out.append(v.toString());
572
        for(String key : v.getPropertyKeys()) {
573
            out.append(key).append(": ").append(v.getProperty(key)).append(" ");
574
        }
575
        ps.println(out.toString());
576
    }
577

    
578
    @Override
579
    public EnumSet<SearchMode> getSearchModes() {
580
        return SEARCH_MODES;
581
    }
582

    
583
    @Override
584
    public boolean isSupportedIdentifier(String value) {
585
        return IdentifierUtils.checkURI(value);
586
    }
587

    
588
}
(5-5/12)