/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.rdf.internal.constraints; import java.util.Map; import org.openrdf.model.Literal; import org.openrdf.model.Value; import com.bigdata.bop.AbstractAccessPathOp; import com.bigdata.bop.BOp; import com.bigdata.bop.BOpBase; import com.bigdata.bop.BOpContextBase; import com.bigdata.bop.ContextBindingSet; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IValueExpression; import com.bigdata.bop.NV; import com.bigdata.rdf.error.SparqlTypeErrorException; import com.bigdata.rdf.internal.ILexiconConfiguration; import com.bigdata.rdf.internal.IV; import com.bigdata.rdf.internal.IVCache; import com.bigdata.rdf.internal.NotMaterializedException; import com.bigdata.rdf.lexicon.LexiconRelation; import com.bigdata.rdf.model.BigdataLiteral; import com.bigdata.rdf.model.BigdataValue; import com.bigdata.rdf.model.BigdataValueFactory; import com.bigdata.rdf.model.BigdataValueFactoryImpl; import com.bigdata.rdf.sparql.ast.DummyConstantNode; import com.bigdata.rdf.sparql.ast.FilterNode; import com.bigdata.rdf.sparql.ast.GlobalAnnotations; /** * A specialized IValueExpression that evaluates to an IV. The inputs are * usually, but not strictly limited to, IVs as well. This class also contains * many useful helper methods for evaluation, including providing access to * the BigdataValueFactory and LexiconConfiguration. */ public abstract class IVValueExpression<T extends IV> extends BOpBase implements IValueExpression<T> { /** * */ private static final long serialVersionUID = -7068219781217676085L; public interface Annotations extends BOpBase.Annotations { /** * The namespace of the lexicon. */ public String NAMESPACE = IVValueExpression.class.getName() + ".namespace"; /** * The timestamp of the query. */ public String TIMESTAMP = IVValueExpression.class.getName() + ".timestamp"; } /** * * Note: The double-checked locking pattern <em>requires</em> the keyword * <code>volatile</code>. */ private transient volatile BigdataValueFactory vf; /** * Note: The double-checked locking pattern <em>requires</em> the keyword * <code>volatile</code>. */ private transient volatile ILexiconConfiguration<BigdataValue> lc; /** * Used by subclasses to create the annotations object from the global * annotations and the custom annotations for the particular VE. * * @param globals * The global annotations, including the lexicon namespace. * @param anns * Any additional custom annotations. */ protected static Map<String, Object> anns( final GlobalAnnotations globals, final NV... anns) { final int size = 2 + (anns != null ? anns.length : 0); final NV[] nv = new NV[size]; nv[0] = new NV(Annotations.NAMESPACE, globals.lex); nv[1] = new NV(Annotations.TIMESTAMP, globals.timestamp); if (anns != null) { for (int i = 0; i < anns.length; i++) { nv[i+2] = anns[i]; } } return NV.asMap(nv); } /** * Zero arg convenience constructor. * * @param globals * The global annotations, including the lexicon namespace. * @param anns * Any additional custom annotations. */ public IVValueExpression(final GlobalAnnotations globals, final NV... anns) { this(BOpBase.NOARGS, anns(globals, anns)); } /** * One arg convenience constructor. * * @param globals * The global annotations, including the lexicon namespace. * @param anns * Any additional custom annotations. */ public IVValueExpression(final IValueExpression<? extends IV> x, final GlobalAnnotations globals, final NV... anns) { this(new BOp[] { x }, anns(globals, anns)); } /** * Required shallow copy constructor. */ public IVValueExpression(final BOp[] args, final Map<String, Object> anns) { super(args, anns); if (areGlobalsRequired()) { if (getProperty(Annotations.NAMESPACE) == null) { throw new IllegalArgumentException("must set the lexicon namespace"); } if (getProperty(Annotations.TIMESTAMP) == null) { throw new IllegalArgumentException("must set the query timestamp"); } } } /** * Constructor required for {@link com.bigdata.bop.BOpUtility#deepCopy(FilterNode)}. */ public IVValueExpression(final IVValueExpression<T> op) { super(op); if (areGlobalsRequired()) { if (getProperty(Annotations.NAMESPACE) == null) { throw new IllegalArgumentException("must set the lexicon namespace"); } if (getProperty(Annotations.TIMESTAMP) == null) { throw new IllegalArgumentException("must set the query timestamp"); } } } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public IValueExpression<? extends IV> get(final int i) { try { return (IValueExpression<? extends IV>) super.get(i); } catch (ClassCastException ex) { throw new SparqlTypeErrorException(); } } /** * Returns <code>true</code> unless overridden, meaning the * {@link GlobalAnnotations} are required for this value expression * (certain boolean value expressions do not require them). Global * annotations allow the method getValueFactory and getLexiconConfiguration * to work. */ protected boolean areGlobalsRequired() { return true; } /** * Return the {@link BigdataValueFactory} for the {@link LexiconRelation}. * <p> * Note: This is lazily resolved and then cached. */ protected BigdataValueFactory getValueFactory() { if (vf == null) { synchronized (this) { if (vf == null) { final String namespace = getNamespace(); vf = BigdataValueFactoryImpl.getInstance(namespace); } } } return vf; } /** * Return the namespace of the {@link LexiconRelation}. */ protected String getNamespace() { return (String) getRequiredProperty(Annotations.NAMESPACE); } /** * Return the timestamp for the query. */ protected long getTimestamp() { return (Long) getRequiredProperty(Annotations.TIMESTAMP); } /** * Return the {@link ILexiconConfiguration}. The result is cached. The cache * it will not be serialized when crossing a node boundary. * <p> * Note: It is more expensive to obtain the {@link ILexiconConfiguration} * than the {@link BigdataValueFactory} because we have to resolve the * {@link LexiconRelation} view. However, this happens once per function bop * in a query per node, so the cost is amortized. * * @param bset * A binding set flowing through this operator. * * @throws ContextNotAvailableException * if the context was not accessible on the solution. * * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/513"> * Expose the LexiconConfiguration to function BOPs </a> * * TODO This locates the last committed view of the * {@link LexiconRelation}. Unlike {@link AbstractAccessPathOp}, the * {@link LiteralBooleanBOp} does not declares the TIMESTAMP of the * view. We really need that annotation to recover the right view of * the {@link LexiconRelation}. However, the * {@link ILexiconConfiguration} metadata is immutable so it is Ok to * use the last committed time for that view. This is NOT true of if we * were going to read data from the {@link LexiconRelation}. */ protected ILexiconConfiguration<BigdataValue> getLexiconConfiguration( final IBindingSet bset) { if (lc == null) { synchronized (this) { if (lc == null) { if (!(bset instanceof ContextBindingSet)) { /* * This generally indicates a failure to propagate the * context wrapper for the binding set to a new binding * set during a copy (projection), bind (join), etc. It * could also indicate a failure to wrap binding sets * when they are vectored into an operator after being * received at a node on a cluster. */ throw new ContextNotAvailableException(this.toString()); } final BOpContextBase context = ((ContextBindingSet) bset) .getBOpContext(); final String namespace = getNamespace(); // final long timestamp = ITx.READ_COMMITTED; final long timestamp = getTimestamp(); final LexiconRelation lex = (LexiconRelation) context .getResource(namespace, timestamp); lc = lex.getLexiconConfiguration(); if (vf != null) { // Available as an attribute here. vf = lc.getValueFactory(); } } } } return lc; } /** * Return the {@link Literal} for the {@link IV}. * * @param iv * The {@link IV}. * * @return The {@link Literal}. * * @throws SparqlTypeErrorException * if the argument is <code>null</code>. * @throws SparqlTypeErrorException * if the argument does not represent a {@link Literal}. * @throws NotMaterializedException * if the {@link IVCache} is not set and the {@link IV} can not * be turned into a {@link Literal} without an index read. */ @SuppressWarnings("rawtypes") final static public Literal asLiteral(final IV iv) { if (iv == null) throw new SparqlTypeErrorException(); if (!iv.isLiteral()) throw new SparqlTypeErrorException(); if (iv.isInline() && !iv.needsMaterialization()) { return (Literal) iv; } else if (iv.hasValue()) { return (BigdataLiteral) iv.getValue(); } else { throw new NotMaterializedException(); } } /** * Return the {@link Value} for the {@link IV}. * * @param iv * The {@link IV}. * * @return The {@link Value}. * * @throws SparqlTypeErrorException * if the argument is <code>null</code>. * @throws NotMaterializedException * if the {@link IVCache} is not set and the {@link IV} can not * be turned into a {@link Literal} without an index read. */ @SuppressWarnings("rawtypes") final static public Value asValue(final IV iv) { if (iv == null) throw new SparqlTypeErrorException(); if (iv.isInline() && !iv.needsMaterialization()) { return (Value) iv; } else if (iv.hasValue()) { return (BigdataValue) iv.getValue(); } else { throw new NotMaterializedException(); } } /** * Return the {@link String} label for the {@link IV}. * * @param iv * The {@link IV}. * * @return {@link Literal#getLabel()} for that {@link IV}. * * @throws NullPointerException * if the argument is <code>null</code>. * * @throws NotMaterializedException * if the {@link IVCache} is not set and the {@link IV} must be * materialized before it can be converted into an RDF * {@link Value}. */ @SuppressWarnings("rawtypes") final protected String literalLabel(final IV iv) throws NotMaterializedException { return asLiteral(iv).getLabel(); } /** * Get the function argument (a value expression) and evaluate it against * the source solution. The evaluation of value expressions is recursive. * * @param i * The index of the function argument ([0...n-1]). * @param bs * The source solution. * * @return The result of evaluating that argument of this function. * * @throws IndexOutOfBoundsException * if the index is not the index of an operator for this * operator. * * @throws SparqlTypeErrorException * if the value expression at that index can not be evaluated. * * @throws NotMaterializedException * if evaluation encountered an {@link IV} whose {@link IVCache} * was not set when the value expression required a materialized * RDF {@link Value}. */ protected IV getAndCheckLiteral(final int i, final IBindingSet bs) throws SparqlTypeErrorException, NotMaterializedException { final IV<?, ?> iv = getAndCheckBound(i, bs); if (!iv.isLiteral()) throw new SparqlTypeErrorException(); if (iv.needsMaterialization() && !iv.hasValue()) throw new NotMaterializedException(); return iv; } /** * Get the function argument (a value expression) and evaluate it against * the source solution. The evaluation of value expressions is recursive. * * @param i * The index of the function argument ([0...n-1]). * @param bs * The source solution. * * @return The result of evaluating that argument of this function. * * @throws IndexOutOfBoundsException * if the index is not the index of an operator for this * operator. * * @throws SparqlTypeErrorException * if the value expression at that index can not be evaluated. */ protected IV getAndCheckBound(final int i, final IBindingSet bs) throws SparqlTypeErrorException, NotMaterializedException { final IV<?, ?> iv = get(i).get(bs); if (iv == null) throw new SparqlTypeErrorException.UnboundVarException(); return iv; } /** * Combination of {@link #getAndCheckLiteral(int, IBindingSet)} and * {@link #asLiteral(IV)}. */ protected Literal getAndCheckLiteralValue(final int i, final IBindingSet bs) { return asLiteral(getAndCheckLiteral(i, bs)); } /** * Return an {@link IV} for the {@link Value}. * * @param value * The {@link Value}. * @param bs * The bindings on the solution are ignored, but the reference is * used to obtain the {@link ILexiconConfiguration}. * * @return An {@link IV} for that {@link Value}. */ final protected IV asIV(final Value value, final IBindingSet bs) { /* * Convert to a BigdataValue if not already one. * * If it is a BigdataValue, then make sure that it is associated with * the namespace for the lexicon relation. */ if (value instanceof IV) { return (IV) value; } final BigdataValue v = getValueFactory().asValue(value); return asIV(v, bs); } /** * Return an {@link IV} for the {@link Value}. * <p> * If the supplied BigdataValue has an IV, cache the BigdataValue on the * IV and return it. If there is no IV, first check the LexiconConfiguration * to see if an inline IV can be created. As a last resort, create a * "dummy IV" (a TermIV with a "0" reference) for the value. * * @param value * The {@link BigdataValue} * @param bs * The bindings on the solution are ignored, but the reference is * used to obtain the {@link ILexiconConfiguration}. * * @return An {@link IV} for that {@link BigdataValue}. */ protected IV asIV(final BigdataValue value, final IBindingSet bs) { // first check to see if there is already an IV IV iv = value.getIV(); if (iv != null) { // cache the value iv.setValue(value); return iv; } @SuppressWarnings("rawtypes") ILexiconConfiguration lc = null; try { lc = getLexiconConfiguration(bs); } catch (ContextNotAvailableException ex) { // can't access the LC (e.g. some test cases) // log.warn? } // see if we happen to have the value in the vocab or can otherwise // create an inline IV for it if (lc != null) { iv = lc.createInlineIV(value); if (iv != null) { // cache the value only if it's something that would require materialization if (iv.needsMaterialization()) { iv.setValue(value); } return iv; } } // toDummyIV will also necessarily cache the value since the IV // doesn't mean anything return DummyConstantNode.toDummyIV(value); } }