/* * Copyright (c) 2009, 2010, James Leigh All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the openrdf.org nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ package net.enilink.composition.properties.sparql; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.objectweb.asm.Label; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import net.enilink.composition.asm.util.BehaviourMethodGenerator; import net.enilink.composition.exceptions.ConfigException; import net.enilink.composition.properties.PropertyMapper; import net.enilink.composition.properties.annotations.Name; import net.enilink.commons.iterator.IExtendedIterator; import net.enilink.komma.core.IGraph; import net.enilink.komma.core.IEntityManager; import net.enilink.komma.core.IQuery; import net.enilink.komma.core.IStatement; /** * Rewrites the SPARQL query used by sparql behaviour methods by loading * additional properties. * */ public class SPARQLQueryOptimizer { private static final Pattern selectWhere = Pattern.compile( "\\sSELECT\\s+([\\?\\$]\\w+)\\s+WHERE\\s*\\{", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); private static final Type IGRAPH_TYPE = Type.getType(IGraph.class); private static final Type ISTATEMENT_TYPE = Type.getType(IStatement.class); private static final Type OBJECT_ARRAY_TYPE = Type.getType(Object[].class); public void implementQuery(String sparql, String base, PropertyMapper pm, Method method, BehaviourMethodGenerator gen) throws Exception { Class<?> range = method.getReturnType(); boolean primitive = range.isPrimitive(); boolean functional = !range.equals(Set.class); Map<String, String> eager = null; if (functional && !primitive) { eager = pm.findEagerProperties(range); } else if (!primitive) { range = Object.class; java.lang.reflect.Type t = method.getGenericReturnType(); if (t instanceof ParameterizedType) { java.lang.reflect.Type c = ((ParameterizedType) t) .getActualTypeArguments()[0]; if (c instanceof Class<?>) { range = (Class<?>) c; eager = pm.findEagerProperties((Class<?>) c); } } } implementQuery(sparql, base, eager, range, functional, method, gen); } protected void setParameters(Method method, BehaviourMethodGenerator gen) throws Exception { for (int i = 0, params = method.getParameterTypes().length; i < params; i++) { boolean named = false; for (Annotation ann : method.getParameterAnnotations()[i]) { if (ann.annotationType().equals(Name.class)) { for (String name : ((Name) ann).value()) { named = true; gen.dup(); gen.push(name); gen.loadArg(i); gen.invoke(IQuery.class.getMethod("setParameter", String.class, Object.class)); } } } if (!named) throw new ConfigException("@name annotation not found: " + method.getName()); } } public void implementQuery(String qry, String base, Map<String, String> eager, Class<?> range, boolean functional, Method method, BehaviourMethodGenerator gen) throws Exception { prepareQuery(qry, base, range, eager, gen); setParameters(method, gen); evaluateQuery(range, functional, gen); } private void prepareQuery(String qry, String base, Class<?> range, Map<String, String> eager, BehaviourMethodGenerator gen) throws Exception { Method prepareMethod = IEntityManager.class.getMethod("createQuery", String.class, String.class); Type rangeType = Type.getType(range); boolean objectQuery = !(IGRAPH_TYPE.equals(rangeType) || ISTATEMENT_TYPE.equals(rangeType) || OBJECT_ARRAY_TYPE .equals(rangeType)); gen.loadThis(); gen.invokeVirtual(gen.getMethod().getOwner().getType(), SparqlBehaviourMethodProcessor.GET_MANAGER); if (objectQuery) { gen.push(optimizeQueryString(qry, eager)); } else { gen.push(qry); } gen.push(base); gen.invoke(prepareMethod); gen.dup(); gen.push("this"); gen.loadBean(); gen.invoke(IQuery.class.getMethod("setParameter", String.class, Object.class)); } /** * @param map * property name to predicate uri or null for datatype */ private String optimizeQueryString(String sparql, Map<String, String> map) { Matcher matcher = selectWhere.matcher(sparql); if (map != null && matcher.find()) { String var = matcher.group(1); int idx = sparql.lastIndexOf('}'); StringBuilder sb = new StringBuilder(256 + sparql.length()); sb.append(sparql, 0, matcher.start(1)); sb.append(var).append(" "); sb.append(var).append("_class").append(" "); for (Map.Entry<String, String> e : map.entrySet()) { String name = e.getKey(); if (name.equals("class")) continue; sb.append(var).append("_").append(name).append(" "); } sb.append(sparql, matcher.end(1), idx); sb.append(" OPTIONAL { ").append(var); sb.append(" a ").append(var).append("_class}"); for (Map.Entry<String, String> e : map.entrySet()) { String pred = e.getValue(); String name = e.getKey(); if (name.equals("class")) continue; sb.append(" OPTIONAL { ").append(var).append(" <"); sb.append(pred).append("> "); sb.append(var).append("_").append(name).append("}"); } sb.append(sparql, idx, sparql.length()); sparql = sb.toString(); } return sparql; } private void evaluateQuery(Class<?> range, boolean functional, BehaviourMethodGenerator gen) throws Exception { Label tryLabel = gen.mark(); // try int result = gen.newLocal(Type.getType(IExtendedIterator.class)); Type rangeType = Type.getType(range); if (IGRAPH_TYPE.equals(rangeType) || ISTATEMENT_TYPE.equals(rangeType)) { gen.push(ISTATEMENT_TYPE); gen.push(0); gen.newArray(Type.getType(Class.class)); gen.invoke(IQuery.class.getMethod("evaluate", Class.class, Class[].class)); } else { gen.invoke(IQuery.class.getMethod("evaluate")); } gen.storeLocal(result); gen.loadLocal(result); gen.invoke(Iterator.class.getMethod("hasNext")); Label noNext = gen.newLabel(); gen.ifZCmp(GeneratorAdapter.EQ, noNext); gen.loadLocal(result); gen.invoke(Iterator.class.getMethod("next")); gen.mark(noNext); gen.push((String) null); Label noException = gen.newLabel(); gen.goTo(noException); // catch (Throwable e) { // throw e; // } Label catchLabel = gen.mark(); Type runtimeException = Type.getType(Throwable.class); gen.catchException(tryLabel, catchLabel, runtimeException); gen.loadLocal(result); gen.invoke(IExtendedIterator.class.getMethod("close")); gen.throwException(); gen.mark(noException); gen.loadLocal(result); gen.invoke(IExtendedIterator.class.getMethod("close")); gen.returnValue(); } }