package org.aksw.qa.commons.sparql; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Properties; import org.apache.jena.query.Query; import org.apache.jena.query.QueryFactory; import org.apache.jena.shared.PrefixMapping; import org.apache.jena.shared.impl.PrefixMappingImpl; import org.apache.jena.sparql.util.FmtUtils; import org.apache.jena.sparql.util.PrefixMapping2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings({ "unchecked", "rawtypes" }) public class SPARQLPrefixResolver { private static Logger LOGGER = LoggerFactory.getLogger(SPARQLPrefixResolver.class); /** * Refers to resource "prefixes.properties". Left had side is a prefix, right hand side is a corresponding URI. * <p> * Prefixes will be loaded during loading of enclosing class, via static constructor. */ private static Properties globalPrefixes = new Properties(); /** * This mapping will be used to refer to, if the local mapping (e.g.PREFIX declarations in the query itself) cannot resolve a prefix. Will be initialized with all prefixes in * {@link #globalPrefixes} */ private static PrefixMapping globalPrefixMapping; static { try { globalPrefixes.load(SPARQLPrefixResolver.class.getClassLoader().getResourceAsStream("prefixes.properties")); globalPrefixMapping = new PrefixMappingImpl(); if (globalPrefixes != null) { globalPrefixMapping.setNsPrefixes((Map) globalPrefixes); } LOGGER.debug("Loaded prefixes.properties. Key count: " + globalPrefixes.size()); } catch (IOException e) { LOGGER.error("Couldn't find resource file: prefixes.properties", e); } } /** * If you want to add/remove a prefix, this is your best bet. * * @return The global prefix mapping, possibly containing all from resource "prefixes.properties" */ public static PrefixMapping getGlobalPrefixMapping() { return globalPrefixMapping; } /** * Adds the declaration of a prefix, if its used in the sparql query, but not declared. As global prefix source {@link #globalPrefixMapping} is used, which is initialized with data from resource * "prefixes.properties" * <p> * In query defined prefixes override. * <p> * If uris are given in the query body which can be represented with a prefix, the uris are replaced with a prefix and a prefix declaration will be added. E.g. * "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" is present, it will be replaced with "rdf:type", and prefix declaration for "rdf" will be added. * <p> * This will not remove unused prefix declarations. * <p> * if you wonder why keyword "a" is replaced, this is correct behavior. This is, because mapping for rdf: is set in a PrefixMap(its default in global). * * @param sparqlQuery * a sparql query * @return the same sparql query, but with missing PREFIX declarations added. */ public static String addMissingPrefixes(final String sparqlQuery) { /** * Create a custom two stage prefix mapping. Global mappings are those defined in prefixes.properties, local mappings are those present in the querystring. Single global mappings will be added * to local mappings if 1) local cannot resolve it 2) global can resolve it, 3) it is deemed as a correct mapping for given uri by the parser. */ PrefixMapping2 pmap = new TwoStagePrefixMapping(globalPrefixMapping); /** * Create empty query, set custom mapping */ Query q1 = QueryFactory.create(); q1.setPrefixMapping(pmap); /** * Parse the string. if e.g. rdf:type is present in query, but there was no ""PREFIX rdf:" , they will be resolved durig this process. */ q1 = QueryFactory.parse(q1, sparqlQuery, null, null); /** * If uris are given in the actual query which can be represented with a prefix, the uris are replaced with a prefix and a prefix declaration will be added. E.g. * "<http://www.w3.org/1999/02/22-rdf-syntax-ns#>" is present, it will be replaced with "rdf:", and prefix declaration for "rdf" will be added. * <p> * We have to call toString twice, because the serialization of the query {@Link org.apache.jena.sparql.core.Prologue} is done before serializing the query body (thus replacing URIS and * setting prefixes in mapping). so, query gets replaced, but the already done prefix declaration strings are not modified. After first toString call, all prefixes are set in the mapping, * allowing for adequate serialization now. */ q1.toString(); return q1.toString(); } /** * Consists of two, a local and a global request mapping. If something is requested, the request goes first to the local mapping. If the local mapping cant respond, the global mapping is asked. If * the global mapping has a valid answer, it will be written to the local mapping */ static class TwoStagePrefixMapping extends PrefixMapping2 { /** * references to {@link FmtUtils.checkValidPrefixName(String)} Checks, if a prefixed string is valid. Unfortunately, this method is private. Well, a java hack aint a java hack without a little * reflection ¯\_(ツ)_/¯ */ private static Method checkValidPrefixMethod; static { try { checkValidPrefixMethod = FmtUtils.class.getDeclaredMethod("checkValidPrefixName", String.class); checkValidPrefixMethod.setAccessible(true); } catch (NoSuchMethodException | SecurityException e) { e.printStackTrace(); } } public TwoStagePrefixMapping(final PrefixMapping globalPrefixes) { super(globalPrefixes); } @Override public String getNsPrefixURI(final String prefix) { String s = super.getLocalPrefixMapping().getNsPrefixURI(prefix); if (s != null) { return s; } PrefixMapping pmapGlobal = super.getGlobalPrefixMapping(); s = pmapGlobal.getNsPrefixURI(prefix); if (s != null) { super.getLocalPrefixMapping().setNsPrefix(prefix, s); return s; } return null; } @Override public String getNsURIPrefix(final String uri) { String s = super.getLocalPrefixMapping().getNsURIPrefix(uri); if (s != null) { return s; } PrefixMapping pmapGlobal = super.getGlobalPrefixMapping(); if (pmapGlobal == null) { return null; } if (pmapGlobal != null) { s = pmapGlobal.getNsURIPrefix(uri); } super.getLocalPrefixMapping().setNsPrefix(s, uri); return null; } @Override public PrefixMapping removeNsPrefix(final String prefix) { super.getLocalPrefixMapping().removeNsPrefix(prefix); return this; } @Override public String expandPrefix(final String prefixed) { String s = super.getLocalPrefixMapping().expandPrefix(prefixed); PrefixMapping pmapGlobal = super.getGlobalPrefixMapping(); if (pmapGlobal == null) { return s; } if (s == null || s.equals(prefixed)) { if (pmapGlobal != null) { s = pmapGlobal.expandPrefix(prefixed); } if (s != null) { int colon = prefixed.indexOf(':'); String prefix = prefixed.substring(0, colon); String uri = pmapGlobal.getNsPrefixURI(prefix); super.getLocalPrefixMapping().setNsPrefix(prefix, uri); } } return s; } /** @see org.apache.jena.shared.PrefixMapping#shortForm(java.lang.String) */ @Override public String shortForm(final String uri) { PrefixMapping pmapLocal = super.getLocalPrefixMapping(); PrefixMapping pmapGlobal = super.getGlobalPrefixMapping(); String s = pmapLocal.shortForm(uri); if (pmapGlobal == null) { return s; } if (s == null || s.equals(uri)) { s = pmapGlobal.shortForm(uri); if (s != null && !s.equals(uri)) { boolean b = false; try { b = (boolean) checkValidPrefixMethod.invoke(null, s); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (b) { String prefix = s.substring(0, s.indexOf(":")); pmapLocal.setNsPrefix(prefix, pmapGlobal.getNsPrefixURI(prefix)); } } } return s; } /** @see org.apache.jena.shared.PrefixMapping#qnameFor(java.lang.String) */ @Override public String qnameFor(final String uri) { PrefixMapping pmapLocal = super.getLocalPrefixMapping(); PrefixMapping pmapGlobal = super.getGlobalPrefixMapping(); String s = pmapLocal.qnameFor(uri); if (s != null) { return s; } if (pmapGlobal != null) { s = pmapGlobal.qnameFor(uri); if (s != null) { boolean b = false; try { b = (boolean) checkValidPrefixMethod.invoke(null, s); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (b) { String prefix = s.substring(0, s.indexOf(":")); pmapLocal.setNsPrefix(prefix, pmapGlobal.getNsPrefixURI(prefix)); } } } return s; } @Override public Map<String, String> getNsPrefixMap() { return getNsPrefixMap(false); } @Override public Map<String, String> getNsPrefixMap(final boolean includeGlobalMap) { return super.getNsPrefixMap(false); } @Override public String toString() { return "LocalMapping: " + super.getLocalPrefixMapping().toString(); } } }