/** 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 */ /* * Created on Aug 18, 2011 */ package com.bigdata.rdf.sparql.ast.service; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.openrdf.model.URI; import com.bigdata.bop.BOp; import com.bigdata.bop.IConstant; import com.bigdata.bop.IVariable; import com.bigdata.bop.IVariableOrConstant; import com.bigdata.rdf.internal.impl.TermId; import com.bigdata.rdf.model.BigdataValue; import com.bigdata.rdf.sparql.ast.FilterNode; import com.bigdata.rdf.sparql.ast.GraphPatternGroup; import com.bigdata.rdf.sparql.ast.GroupMemberNodeBase; import com.bigdata.rdf.sparql.ast.IGraphPatternContainer; import com.bigdata.rdf.sparql.ast.IGroupMemberNode; import com.bigdata.rdf.sparql.ast.IJoinNode; import com.bigdata.rdf.sparql.ast.JoinGroupNode; import com.bigdata.rdf.sparql.ast.QueryBase; import com.bigdata.rdf.sparql.ast.StaticAnalysis; import com.bigdata.rdf.sparql.ast.TermNode; import com.bigdata.rdf.sparql.ast.UnionNode; import com.bigdata.rdf.store.AbstractTripleStore; /** * An extension point for external service calls which produce solution * multisets (a SPARQL <code>SERVICE</code>). * * TODO It would make the internal APIs significantly easier if we modeled this * as a type of {@link GraphPatternGroup}, similar to {@link JoinGroupNode} and * {@link UnionNode}. */ public class ServiceNode extends GroupMemberNodeBase<IGroupMemberNode> implements IJoinNode, IGraphPatternContainer { private static final long serialVersionUID = 1L; interface Annotations extends GroupMemberNodeBase.Annotations, IJoinNode.Annotations, IGraphPatternContainer.Annotations { /** * The {@link TermNode} for the SERVICE URI (either a simple variable or * a simple constant). */ String SERVICE_REF = "serviceRef"; /** * The namespace of the {@link AbstractTripleStore} instance (not the * namespace of the lexicon relation). This resource will be located and * made available to the {@link ServiceCall}. */ String NAMESPACE = "namespace"; /** * The "SELECT" option. * * TODO Lift out. This is used for many things in SPARQL UPDATE, not * just for SPARQL Federation. */ String SILENT = "silent"; boolean DEFAULT_SILENT = false; /** * The timeout in milliseconds before a SERVICE request is failed. * * @see #DEFAULT_TIMEOUT */ String TIMEOUT = "timeout"; final long DEFAULT_TIMEOUT = Long.MAX_VALUE; /** * The text "image" of the original SPARQL SERVICE clause. The "image" * of the original graph pattern is what gets sent to a remote SPARQL * end point when we evaluate the SERVICE node. Because the original * "image" of the graph pattern is being used, we also need to have the * prefix declarations so we can generate a valid SPARQL request. */ String EXPR_IMAGE = "exprImage"; /** * The prefix declarations for the SPARQL query from which the * {@link #EXPR_IMAGE} was taken. This is needed in order to generate a * valid SPARQL query for a remote SPARQL end point when we evaluate the * SERVICE request. */ String PREFIX_DECLS = "prefixDecls"; /** * The set of variables which can flow in/out of the SERVICE. * * TODO Use the {@link QueryBase.Annotations#PROJECTION} for this? */ String PROJECTED_VARS = "projectedVars"; } /** * Constructor required for {@link com.bigdata.bop.BOpUtility#deepCopy(FilterNode)}. */ public ServiceNode(final ServiceNode op) { super(op); } /** * Required shallow copy constructor. */ public ServiceNode(BOp[] args, Map<String, Object> anns) { super(args, anns); } /** * Construct a function node in the AST. * * @param serviceRef * The value expression for the SERVICE URI. * @param groupNode * The graph pattern used to invoke the service. * * @see ServiceRegistry */ public ServiceNode( final TermNode serviceRef, final GraphPatternGroup<IGroupMemberNode> groupNode) { super(new BOp[] {}, null/* anns */); setServiceRef(serviceRef); setGraphPattern(groupNode); } /** * The service reference. */ public TermNode getServiceRef() { return (TermNode) getRequiredProperty(Annotations.SERVICE_REF); } /** * Set the service reference. */ public void setServiceRef(final TermNode serviceRef) { if(serviceRef == null) throw new IllegalArgumentException(); setProperty(Annotations.SERVICE_REF, serviceRef); } @SuppressWarnings("unchecked") public GraphPatternGroup<IGroupMemberNode> getGraphPattern() { return (GraphPatternGroup<IGroupMemberNode>) getRequiredProperty(Annotations.GRAPH_PATTERN); } @Override public void setGraphPattern( final GraphPatternGroup<IGroupMemberNode> graphPattern) { if (graphPattern == null) throw new IllegalArgumentException(); /* * Clear the parent reference on the new where clause. * * Note: This handles cases where a join group is lifted into a named * subquery. If we do not clear the parent reference on the lifted join * group it will still point back to its parent in the original join * group. */ graphPattern.setParent(null); super.setProperty(Annotations.GRAPH_PATTERN, graphPattern); } /** * Returns <code>false</code>. */ final public boolean isOptional() { return false; } /** * Returns <code>false</code>. */ final public boolean isMinus() { return false; } final public boolean isSilent() { return getProperty(Annotations.SILENT, Annotations.DEFAULT_SILENT); } final public void setSilent(final boolean silent) { setProperty(Annotations.SILENT, silent); } public String getExprImage() { return (String) getProperty(Annotations.EXPR_IMAGE); } /** * Set the text "image" of the SPARQL SERVICE clause. This will be used IFF * we generate a SPARQL query for a remote SPARQL end point. You must also * specify the prefix declarations for that text "image". */ public void setExprImage(final String serviceExpressionString) { setProperty(Annotations.EXPR_IMAGE, serviceExpressionString); } @SuppressWarnings({ "unchecked", "rawtypes" }) public Map<String, String> getPrefixDecls() { return (Map) getProperty(Annotations.PREFIX_DECLS); } /** * Set the prefix declarations for the group graph pattern. This will be * used IFF we generate a SPARQL query for a remote SPARQL end point. You * must also specify the text "image". */ public void setPrefixDecls(final Map<String, String> prefixDecls) { setProperty(Annotations.PREFIX_DECLS, prefixDecls); } /** * * @param projectedVars * * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/510"> * Blank nodes in SERVICE graph patterns </a> */ public void setProjectedVars(final Set<IVariable<?>> projectedVars) { setProperty(Annotations.PROJECTED_VARS, projectedVars); } /** * @see Annotations#PROJECTED_VARS */ @SuppressWarnings("unchecked") public Set<IVariable<?>> getProjectedVars() { return (Set<IVariable<?>>) getProperty(Annotations.PROJECTED_VARS); } public void setTimeout(final Long timeout) { setProperty(Annotations.TIMEOUT, timeout); } /** * Return the timeout for evaluation of this SERVICE request. * * @return The timeout -or- {@link Annotations#DEFAULT_TIMEOUT} if the * timeout was not explicitly configured. * * @see Annotations#TIMEOUT */ public long getTimeout() { return getProperty(Annotations.TIMEOUT, Annotations.DEFAULT_TIMEOUT); } final public List<FilterNode> getAttachedJoinFilters() { @SuppressWarnings("unchecked") final List<FilterNode> filters = (List<FilterNode>) getProperty(Annotations.FILTERS); if (filters == null) { return Collections.emptyList(); } return Collections.unmodifiableList(filters); } final public void setAttachedJoinFilters(final List<FilterNode> filters) { setProperty(Annotations.FILTERS, filters); } // TODO toString() for all attributes. @Override public String toString(int indent) { final StringBuilder sb = new StringBuilder(); final TermNode serviceRef = getServiceRef(); final long timeout = getTimeout(); sb.append("\n"); sb.append(indent(indent)); sb.append("SERVICE"); if(isSilent()) { sb.append(" SILENT"); } if(serviceRef.isConstant()) { sb.append(" <"); sb.append(serviceRef); sb.append(">"); } else { sb.append(" ?"); sb.append(serviceRef); } if (timeout != Long.MAX_VALUE) { sb.append(" [timeout=" + timeout + "ms]"); } if (getGraphPattern() != null) { sb.append(" {"); sb.append(getGraphPattern().toString(indent+1)); sb.append("\n").append(indent(indent)).append("}"); } final List<FilterNode> filters = getAttachedJoinFilters(); if(!filters.isEmpty()) { for (FilterNode filter : filters) { sb.append(filter.toString(indent + 1)); } } if (getQueryHints() != null && !getQueryHints().isEmpty()) { sb.append("\n"); sb.append(indent(indent + 1)); sb.append(Annotations.QUERY_HINTS); sb.append("="); sb.append(getQueryHints().toString()); } return sb.toString(); } @Override public Set<IVariable<?>> getRequiredBound(StaticAnalysis sa) { return getResponsibleServiceFactory().getRequiredBound(this); } @Override public Set<IVariable<?>> getDesiredBound(StaticAnalysis sa) { return getResponsibleServiceFactory().getDesiredBound(this); } /** * Returns the service factory that is responsible for handling this * service node. * * @return the associated {@link ServiceFactory} */ public ServiceFactory getResponsibleServiceFactory() { final ServiceRegistry serviceRegistry = ServiceRegistry.getInstance(); final IVariableOrConstant<?> serviceRef = getServiceRef().getValueExpression(); URI serviceUri = null; // will be set if there is a URI if (serviceRef!=null && serviceRef instanceof IConstant) { final IConstant<?> serviceRefConst = (IConstant<?>)serviceRef; final Object val = serviceRefConst.get(); if (val instanceof TermId<?>) { final TermId<?> valTerm = (TermId<?>)val; final BigdataValue bdVal = valTerm.getValue(); if (bdVal!=null && bdVal instanceof URI) { serviceUri = (URI)bdVal; } } } return serviceRegistry.getServiceFactoryByServiceURI(serviceUri); } }