/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ package org.dspace.rdf.conversion; import com.hp.hpl.jena.rdf.model.Literal; import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.RDFNode; import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.vocabulary.RDF; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; /** * * @author Pascal-Nicolas Becker (dspace -at- pascal -hyphen- becker -dot- de) */ public class MetadataRDFMapping { private static final Logger log = Logger.getLogger(MetadataRDFMapping.class); protected final String name; protected final Pattern fulfills; protected final List<Resource> results; protected MetadataRDFMapping(String name, Pattern fulfills, List<Resource> results) { this.name = name; this.fulfills = fulfills; this.results = results; } public static MetadataRDFMapping getMetadataRDFMapping( Resource mappingResource, String dsoIdentifier) { // For better log message: try to get the uri of this mapping. String uri = null; if (mappingResource.getURI() != null) { uri = " (" + mappingResource.getURI() + ")"; } if (log.isDebugEnabled()) { if (uri.equals("")) { log.debug("Processing blank node MetadataRDFMapping."); } else { log.debug("Processing MetadataRDFMapping" + uri + "."); } } // Parse the property DMRM.metadataName RDFNode nameNode; try { nameNode = getSingularProperty(mappingResource, DMRM.metadataName); } catch (IllegalArgumentException ex) { log.error("The Property 'metadataName' exists multiple times in one " + "DSpaceMetadataRDFMapping, ignoring it" + uri + "."); return null; } if (nameNode == null) { log.error("Cannot find property 'metadataName', ignoring mapping" + uri + "."); return null; } if (!nameNode.isLiteral()) { log.error("Property 'metadataName' is not a literal, ignoring mapping" + uri + "."); return null; } String name = nameNode.asLiteral().getLexicalForm(); log.debug("Found mapping name '" + name + "'."); // Parse the property condition, if it exists. RDFNode conditionNode; try { conditionNode = getSingularProperty(mappingResource, DMRM.condition); } catch (IllegalArgumentException ex) { log.error("There are multiple properties 'condition' in one " + "DSpaceMetadataRDFMapping, ignoring it" + uri + "."); return null; } String regex = null; Pattern condition = null; if (conditionNode != null) { if (conditionNode.isLiteral()) { regex = conditionNode.asLiteral().getLexicalForm(); log.debug("Found property condition '" + regex + "'."); } else { log.error("Property 'condition' is not a literal, ignoring " + "mapping" + uri + "."); return null; } } else { // there is no property "condition". As this property is optional // there is nothing to be done here. log.debug("Didn't find a property \"condition\"."); } if (regex != null) { try { condition = Pattern.compile(regex); } catch (PatternSyntaxException ex) { log.error("Property 'condition' does not specify a valid java " + "regex pattern. Will ignore mapping" + uri + ".", ex); return null; } } // parse all properties DMRM.creates. List<Resource> results = new ArrayList<>(); StmtIterator mappingIter = mappingResource.listProperties(DMRM.creates); if (!mappingIter.hasNext()) { log.warn("No 'creates' property in a DSpaceMetadataRDFMapping, " + "ignonring it" + uri + "."); return null; } while (mappingIter.hasNext()) { RDFNode result = mappingIter.nextStatement().getObject(); if (!result.isResource()) { log.error("Mapping result" + uri + " is a Literal not a resource. " + "Ignoring mapping."); return null; } results.add(result.asResource()); } // create mapping return new MetadataRDFMapping(name, condition, results); } public boolean matchesName(String name) { return StringUtils.equalsIgnoreCase(this.name, name); } public boolean fulfills(String value) { // if fulfills exists, we have to check the field value if (this.fulfills == null) { return true; } if (!this.fulfills.matcher(value).matches()) { log.debug("Value '" + value + "' does not match regex '" + fulfills.toString() + "'."); return false; } else { return true; } //return this.fulfills.matcher(value).matches(); } public void convert(String value, String lang, String dsoIRI, Model m) { log.debug("Using convertion for field " + name + " on value: " + value + " for " + dsoIRI + "."); // run over all results for (Iterator<Resource> iter = this.results.iterator() ; iter.hasNext() ; ) { try { compileResult(m, iter.next(), dsoIRI, name, value, lang); } catch (MetadataMappingException ex) { log.error(ex.getMessage() + " Will ignore this mapping result."); } } } protected void compileResult(Model m, Resource result, String dsoIRI, String name, String value, String lang) throws MetadataMappingException { // for better debug messages. String uri = ""; if (result.isURIResource()) uri = " (" + result.getURI() + ")"; // check the subject RDFNode subjectNode; try { subjectNode = getSingularProperty(result, DMRM.subject); } catch (IllegalArgumentException ex) { throw new MetadataMappingException("There are multiple 'subject' " + "properties in a mapping result" + uri + "."); } if (subjectNode == null) { throw new MetadataMappingException("Mapping result" + uri + " does not have a subject."); } if (!subjectNode.isResource()) { throw new MetadataMappingException("Subject of a result" + uri + " is a Literal not a URIResource."); } log.debug("Found subject: " + subjectNode.toString()); // check the predicate RDFNode predicateNode; try { predicateNode = getSingularProperty(result, DMRM.predicate); } catch (IllegalArgumentException ex) { throw new MetadataMappingException("There are multiple 'predicate' " + "properties in a mapping result" + uri + "."); } if (predicateNode == null) { throw new MetadataMappingException("Mapping result" + uri + " does not have a predicate."); } if (!predicateNode.isResource()) { throw new MetadataMappingException("Predicate of a result" + uri + " is a Literal not a URIResource."); } log.debug("Found predicate: " + predicateNode.toString()); RDFNode objectNode; try { objectNode = getSingularProperty(result, DMRM.object); } catch (IllegalArgumentException ex) { throw new MetadataMappingException("There are multiple 'object' " + "properties in a mapping result" + uri + "."); } if (objectNode == null) { throw new MetadataMappingException("Mapping result" + uri + " does not have a object."); } log.debug("Found object: " + objectNode.toString()); Resource subject = parseSubject(m, subjectNode.asResource(), dsoIRI, name, value); if (subject == null) { throw new MetadataMappingException("Cannot parse subject of a " + "reified statement " + uri + "."); } Property predicate = parsePredicate(m, predicateNode.asResource(), dsoIRI, name, value); if (predicate == null) { throw new MetadataMappingException("Cannot parse predicate of a " + "reified statement " + uri + "."); } RDFNode object = parseObject(m, objectNode, dsoIRI, name, value, lang); if (object == null) { throw new MetadataMappingException("Cannot parse object of a " + "reified statement " + uri + "."); } m.add(subject, predicate, object); } protected Resource parseSubject(Model m, Resource subject, String dsoIRI, String name, String value) { if (subject.hasProperty(RDF.type, DMRM.ResourceGenerator)) { String generatedIRI = parseResourceGenerator(subject, value, dsoIRI); if (generatedIRI == null) { log.debug("Generated subject IRI is null."); return null; } log.debug("Subject ResourceGenerator generated '" + generatedIRI + "'."); return m.createResource(generatedIRI); } return subject; } protected Property parsePredicate(Model m, Resource predicate, String dsoIRI, String name, String value) { if (predicate.hasProperty(RDF.type, DMRM.ResourceGenerator)) { String generatedIRI = parseResourceGenerator(predicate, value, dsoIRI); if (generatedIRI == null) { log.debug("Generated predicate IRI is null."); return null; } log.debug("Property ResourceGenerator generated '" + generatedIRI + "'."); return m.createProperty(generatedIRI); } String uri = predicate.getURI(); if (uri == null) { log.debug("A result predicate is blank node, but not a " + "ResourceGenerator. Ingoring this result."); return null; } return m.createProperty(uri); } protected RDFNode parseObject(Model m, RDFNode objectNode, String dsoIRI, String name, String value, String lang) { if (objectNode.isLiteral()) return objectNode; Resource object = objectNode.asResource(); if (object.hasProperty(RDF.type, DMRM.LiteralGenerator)) { Literal literalValue = parseLiteralGenerator(m, object, value, lang); if (literalValue == null) return null; return literalValue; } if (object.hasProperty(RDF.type, DMRM.ResourceGenerator)) { String generatedIRI = parseResourceGenerator(object, value, dsoIRI); if (generatedIRI == null) { log.debug("Generated predicate IRI is null."); return null; } log.debug("Property ResourceGenerator generated '" + generatedIRI + "'."); return m.createProperty(generatedIRI); } if (object.isAnon()) { Resource blank = m.createResource(); StmtIterator iter = object.listProperties(); while (iter.hasNext()) { Statement stmt = iter.nextStatement(); Property predicate = stmt.getPredicate(); // iterate recursive over the object of a blank node. blank.addProperty(predicate, parseObject(m, stmt.getObject(), dsoIRI, name, value, lang)); } return blank; } // object is not a literal, is not a blank node, is neither a // IRIGenerator nor a LiteralGenerator => it must be a Resource => use // it as it is. return object; } protected String parseResourceGenerator(Resource resourceGenerator, String value, String dsoIRI) { if (resourceGenerator.isURIResource() && resourceGenerator.equals(DMRM.DSpaceObjectIRI)) { return dsoIRI; } return parseValueProcessor(resourceGenerator, value); } protected Literal parseLiteralGenerator(Model m, Resource literalGenerator, String value, String lang) { if (literalGenerator.isURIResource() && literalGenerator.equals(DMRM.DSpaceValue)) { return m.createLiteral(value); } String modifiedValue = parseValueProcessor(literalGenerator, value); if (modifiedValue == null) return null; // check if we should produce a typed literal // Up the RDF spec lang tags are not significant on typed literals, so // we can ignore them if we have a typed literal. try { RDFNode literalTypeNode = getSingularProperty(literalGenerator, DMRM.literalType); if (literalTypeNode != null) { if (literalTypeNode.isURIResource()) { return m.createTypedLiteral(modifiedValue, literalTypeNode.asResource().getURI()); } else { log.warn("A LiteralGenerator has a property 'literalType' that " + "either is a blank node or a Literal. Ignoring it."); } } } catch (IllegalArgumentException ex) { log.error("A LiteralGenerator has multiple properties " + "'literalType'. Will ignore them."); } // check if a language tag should be generated String languageTag = null; try { RDFNode langNode = getSingularProperty(literalGenerator, DMRM.literalLanguage); if (langNode != null) { if (langNode.isLiteral()) { languageTag = langNode.asLiteral().getLexicalForm(); } else { log.warn("Found a property 'literalLanguage', but its " + "object is not a literal! Ignoring it."); } } } catch (IllegalArgumentException ex) { log.warn("A LiteralGenerator has multiple properties " + "'literalLanguage'. Will ignore them."); } try { RDFNode dspaceLangNode = getSingularProperty(literalGenerator, DMRM.dspaceLanguageTag); if (dspaceLangNode != null) { boolean useDSpaceLang = false; if (dspaceLangNode.isLiteral()) { try { useDSpaceLang = dspaceLangNode.asLiteral().getBoolean(); } catch (Exception ex) { /* * nothing to do here. * * this is for sure not the best coding style, but the * one that works best here as jena throws some undeclared * RuntimeExceptions if the detection of the boolean fails. */ } } if (useDSpaceLang && !StringUtils.isEmpty(lang)) { if (lang.indexOf("_") == 2) { languageTag = lang.replaceFirst("_", "-"); } else { languageTag = lang; } } } } catch (IllegalArgumentException ex) { log.error("A LiteralGenerator has multiple properties " + "'dspaceLanguageTag'. Will ignore them."); } if (languageTag != null) return m.createLiteral(modifiedValue, languageTag); return m.createLiteral(modifiedValue); } protected String parseValueProcessor(Resource valueProcessor, String value) { // look if there's a modifier. RDFNode modifierNode; try { modifierNode = getSingularProperty(valueProcessor, DMRM.modifier); } catch (IllegalArgumentException ex) { log.error("The ResourceGenerator of a mapping result has " + "multiple 'modifier' properties, skipping this result."); return null; } if (modifierNode != null) { // in case there is a modifier find its matcher, its replacement and // modifies the value if (!modifierNode.isResource()) { log.error("The modifier of a result is a Literal not an Resource! " + "Ingoring this result."); return null; } Resource modifier = modifierNode.asResource(); RDFNode matcherNode; try { matcherNode = getSingularProperty(modifier, DMRM.matcher); } catch (IllegalArgumentException ex) { log.error("The modifier of a mapping result has multiple " + "'matcher' properties. Ignoring this result."); return null; } if (matcherNode == null) { log.error("Found a modifier property to a result, but no " + "matcher property! Ignoring this result!"); return null; } if (!matcherNode.isLiteral()) { log.error("A matcher of a result modifier is not a Literal! " + "Ignoring this result."); return null; } // get the replacement string RDFNode replacementNode; try { replacementNode = getSingularProperty(modifier, DMRM.replacement); } catch (IllegalArgumentException ex) { log.error("The modifier of a mapping result has multiple " + "'replacement' properties. Ignoring this result."); return null; } if (replacementNode == null) { log.error("Found a modifier property to a result, but no " + "replacement property! Ignoring this result!"); return null; } if (!replacementNode.isLiteral()) { log.error("A replacement of a result modifier is not a Literal! " + "Ignoring this result."); return null; } String matcher = matcherNode.asLiteral().getLexicalForm(); String replacement = replacementNode.asLiteral().getLexicalForm(); try { Pattern pattern = Pattern.compile(matcher); String modifiedValue = pattern.matcher(value).replaceAll(replacement); log.debug("Found matcher '" + matcher + "'.\n" + "Found replacement '" + replacement + "'.\n" + "modified '" + value + "' => '" + modifiedValue + "'."); value = modifiedValue; } catch (PatternSyntaxException ex) { log.error("Property 'matcher' of a ValueModifider didn't specify a " + "valid java regex pattern. Will ignore this result.", ex); return null; } } // in case there is a modifier, we modified the value. Insert the // (possibly modified) value in the pattern RDFNode patternNode; try { patternNode = getSingularProperty(valueProcessor, DMRM.pattern); } catch (IllegalArgumentException ex) { log.error("The ValueProcessor of a mapping result has " + "multiple 'pattern' properties, skipping this result."); return null; } if (patternNode == null) { log.debug("Cannot find the property 'pattern' of a " + "ValueProcessor, will use \"$DSpaceValue\"."); patternNode = valueProcessor.getModel().createLiteral("$DSpaceValue"); } if (!patternNode.isLiteral()) { log.error("A 'pattern' property of a ValueProcessor is not a " + "Literal! Skipping this result."); return null; } String pattern = patternNode.asLiteral().getLexicalForm(); String result = pattern.replace("$DSpaceValue", value); log.debug("Found pattern " + pattern + ".\n" + "Created result: " + result); return result; } protected static RDFNode getSingularProperty(Resource r, Property p) throws IllegalArgumentException { List<Statement> stmts = r.listProperties(p).toList(); if (stmts.isEmpty()) { return null; } if (stmts.size() > 1) { throw new IllegalArgumentException("Property '" + p.getURI() + "' exists multiple times."); } return stmts.get(0).getObject(); } }