All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.joseki.processors.SPARQL Maven / Gradle / Ivy

/*
 * (c) Copyright 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP
 * All rights reserved.
 * [See end of file]
 */

package org.joseki.processors;

import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.joseki.*;
import org.joseki.module.Loadable;
import org.joseki.util.GraphUtils;

import com.hp.hpl.jena.query.*;
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.shared.*;
import com.hp.hpl.jena.util.FileManager;

public class SPARQL extends ProcessorBase implements Loadable
{
    // TODO Refactor into the stages of a query 
    private static Logger log = LoggerFactory.getLogger(SPARQL.class) ;
    
    static final String policyMRSW  = JosekiVocab.lockingPolicyMRSW.getURI();
    static final String policyMutex = JosekiVocab.lockingPolicyMutex.getURI();
    static final String policyNone  = JosekiVocab.lockingPolicyNone.getURI();
    
    static final Property allowDatasetDescP = JosekiVocab.allowExplicitDataset ;
    static final Property allowWebLoadingP  = JosekiVocab.allowWebLoading ;
    
    static private Model m = ModelFactory.createDefaultModel() ;
    
    static final Literal XSD_TRUE   = m.createTypedLiteral(true) ; 
    static final Literal XSD_FALSE  = m.createTypedLiteral(false) ;
    
    public static final String P_QUERY          = "query" ;
    public static final String P_QUERY_REF      = "query-uri" ;
    public static final String P_NAMED_GRAPH    = "named-graph-uri" ;
    public static final String P_DEFAULT_GRAPH  = "default-graph-uri" ;
    
    protected boolean allowDatasetDesc = false ;
    protected boolean allowWebLoading  = false ;
    
    protected int maxTriples = 10000 ;
    protected FileManager fileManager ; 
    
    
    public SPARQL()
    {
    }
    
    public void init(Resource processor, Resource implementation)
    {
        log.info("SPARQL processor") ;
        
        fileManager = new FileManager() ;
        
        //fileManager = FileManager.get() ; // Needed for DAWG tests - but why?

        // Only know how to handle http URLs 
        fileManager.addLocatorURL() ;
        
        if ( processor.hasProperty(allowDatasetDescP, XSD_TRUE) )
            allowDatasetDesc = true ;
        if ( processor.hasProperty(allowWebLoadingP, XSD_TRUE) )
            allowWebLoading = true ;

        if ( ! processor.hasProperty(JosekiVocab.lockingPolicy) )
        {
            log.info("Locking policy not declared - default to mutex") ;
            setLock(new LockMutex()) ;
        }
        
        if ( processor.hasProperty(JosekiVocab.lockingPolicy) )
        {
            RDFNode policy = processor.getProperty(JosekiVocab.lockingPolicy).getObject() ;

            if ( ! policy.isURIResource() )
            {
                log.warn("Locking policy is not a URI") ;
                setLock(new LockMutex()) ;
            }
            else
            {
                String policyURI = ((Resource)policy).getURI() ;
    
                if ( policyURI.equals(policyMRSW) )
                {
                    log.info("Locking policy: multiple reader, single writer") ;
                    setLock(new LockMRSW()) ;
                }
                else if (policyURI.equals(policyMutex) )
                {
                    log.info("Locking policy: single request") ;
                    setLock(new LockMutex()) ;
                }
                else if (policyURI.equals(policyNone) )
                {
                    log.info("Locking policy: none") ;
                    setLock(new LockMutex()) ;
                }
                else
                {
                    log.warn("Unrecognized locking policy: <"+policyURI+">") ;
                    setLock(new LockMutex()) ;
                }
            }
        }
        
        log.info("Dataset description: "+allowDatasetDesc+" // Web loading: "+allowWebLoading) ;
    }
    
    @Override
    public void execOperation(Request request, Response response, Dataset dataset) throws QueryExecutionException
    {
        execQueryProtected(request, response, dataset, 0) ;
    }
    
    public void execQueryProtected(Request request, Response response, Dataset dataset, int attempts) throws QueryExecutionException
    {
        try {
            execQueryWorker(request, response, dataset) ;
        }
        catch (QueryExecutionException qEx)
        { throw qEx; }
        catch (QueryException qEx)
        {
            log.info("Query execution error: "+qEx) ;
            QueryExecutionException qExEx = new QueryExecutionException(ReturnCodes.rcQueryExecutionFailure, qEx.getMessage()) ;
            throw qExEx ;
        }
        catch (NotFoundException ex)
        {
            // Trouble loading data
            log.info(ex.getMessage()) ;
            QueryExecutionException qExEx = new QueryExecutionException(ReturnCodes.rcResourceNotFound, ex.getMessage()) ;
            throw qExEx ;
        }
        catch (QueryStageException ex)
        {
            if ( attempts == 0 && causeLooksRetryable(ex) )
            {
                attempts ++ ;
                log.warn("Execution failure (retryable) : retry: "+attempts) ;
                execQueryProtected(request, response, dataset, attempts) ;
                return ;
            }
            
            log.warn("QueryStageException: "+ex.getMessage(), ex) ;
            QueryExecutionException qExEx = new QueryExecutionException(ReturnCodes.rcInternalError, ex.getMessage()) ;
            throw qExEx ;
        }
        catch (JenaException ex)
        {
            log.info("JenaException: "+ex.getMessage()) ;
            QueryExecutionException qExEx = new QueryExecutionException(ReturnCodes.rcJenaException, ex.getMessage()) ;
            throw qExEx ;
        }
        catch (Throwable ex)
        {   // Attempt to catch anything
            log.info("Throwable: "+ex.getMessage(), ex) ;
            //log.info("Throwable: "+ex.getMessage()) ;
            QueryExecutionException qExEx = new QueryExecutionException(ReturnCodes.rcInternalError, ex.getMessage()) ;
            throw qExEx ;
        }
    }
    
    private boolean causeLooksRetryable(Throwable ex)
    {
        while ( ex != null )
        {
            String name = ex.getClass().getName() ;
            if ( name.equals("com.mysql.jdbc.CommunicationsException") ||
                 name.equals("com.mysql.jdbc.exceptions.jdbc4.CommunicationsException") )
                return true ;
            ex = ex.getCause() ;
        }
        return false ;
    }
    
    
    private void execQueryWorker(Request request, Response response, Dataset defaultDataset) throws QueryExecutionException
    {
        //log.info("Request: "+request.paramsAsString()) ;
        String queryString = null ;
        
        if ( request.containsParam(P_QUERY) )
        {
            queryString = request.getParam(P_QUERY) ;
            if  (queryString == null )
            {
                log.debug("No query argument (but query parameter exists)") ;
                throw new JosekiServerException("Query string is null") ;
            }
        }
        
        if ( request.containsParam(P_QUERY_REF) )
        {
            String queryURI = request.getParam(P_QUERY_REF) ;
            if ( queryURI == null )
            {
                log.debug("No query reference argument (but query parameter exists)") ;
                throw new JosekiServerException("Query reference is null") ;
            }
            queryString = getRemoteString(queryURI) ;
        }
        
        if ( queryString == null )
        {
            log.debug("No query argument") ;
            throw new QueryExecutionException(ReturnCodes.rcBadRequest,
            "No query string");    
        }
        
        
        if ( queryString.equals("") )
        {
            log.debug("Empty query string") ;
            throw new QueryExecutionException(ReturnCodes.rcBadRequest,
            "Empty query string");    
        }
        // ---- Query
        
        String queryStringLog = formatForLog(queryString) ;
        log.info("Query: "+queryStringLog) ;
        
        Query query = null ;
        try {
            // NB syntax is ARQ (a superset of SPARQL)
            query = QueryFactory.create(queryString, Syntax.syntaxARQ) ;
        } catch (QueryException ex)
        {
            String tmp = queryString +"\n\r" + ex.getMessage() ;
            throw new QueryExecutionException(ReturnCodes.rcQueryParseFailure, "Parse error: \n"+tmp) ;
        } catch (Throwable thrown)
        {
            log.info("Query unknown error during parsing: "+queryStringLog, thrown) ;
            throw new QueryExecutionException(ReturnCodes.rcQueryParseFailure, "Unknown Parse error") ;
        }
        
        // Check arguments
        Dataset dataset = null ;
        boolean useQueryDesc = false ;
        if ( ! allowDatasetDesc )
        {
            // Restrict to service dataset only. 
            if ( datasetInProtocol(request) )
                throw new QueryExecutionException(ReturnCodes.rcArgumentError, "This service does not allow the dataset to be specified in the protocol request") ;
            if ( query.hasDatasetDescription() )
                throw new QueryExecutionException(ReturnCodes.rcArgumentError, "This service does not allow the dataset to be specified in the query") ;
        }
        else
        {
            // Protocol
            dataset = datasetFromProtocol(request) ;
            // In query itself.
            if ( dataset == null )
            {
                // No dataset in protocol
                if ( query.hasDatasetDescription() )
                    useQueryDesc = true ;
                // If in query, then the query engine will do the loading.
            }
        }
        // Use the service dataset description if
        // not in query and not in protocol. 
        if ( !useQueryDesc && dataset == null )
                dataset = defaultDataset ;

        if ( useQueryDesc )
            // If using query description, ignore dataset
            dataset = null ;
        
        final QueryExecution qexec = getQueryExecution(query, dataset) ;
        ResponseCallback cb = new ResponseCallback(){

            public void callback(boolean successfulOperation)
            { 
                log.debug("ResponseCallback: close execution") ;
                qexec.close(); 
            }} ;
        
        response.addCallback(cb) ;
        executeQuery(query, queryStringLog, qexec, response) ;
        
    }
    
    protected QueryExecution getQueryExecution(Query query, Dataset dataset)
    {
        return QueryExecutionFactory.create(query, dataset) ;
    }
    
    private void executeQuery(Query query, String queryStringLog, QueryExecution qexec, Response response)
        throws QueryExecutionException
    {
        if ( query.isSelectType() )
        {
            // Force some query execute now.
            // To cope with MySQL comms timeouts.  Mutter, mutter.
            ResultSet rs = qexec.execSelect() ;
            
            // Do this to force the query to do something that should touch any underlying database,
            // and hence ensure the communications layer is working.  MySQL can time out after  
            // 8 hours of an idle connection
            rs.hasNext() ;
            
            // Old way - heavyweight
            //rs = ResultSetFactory.copyResults(rs) ;
            response.setResultSet(rs) ;
            log.info("OK/select: "+queryStringLog) ;
            return ;
        }
        
        if ( query.isConstructType() )
        {
            Model model = qexec.execConstruct() ;
            response.setModel(model) ;
            log.info("OK/construct: "+queryStringLog) ;
            return ;
        }
        
        if ( query.isDescribeType() )
        {
            Model model = qexec.execDescribe() ;
            response.setModel(model) ;
            log.info("OK/describe: "+queryStringLog) ;
            return ;
        }
        
        if ( query.isAskType() )
        {
            boolean b = qexec.execAsk() ;
            response.setBoolean(b) ;
            log.info("OK/ask: "+queryStringLog) ;
            return ;
        }
        
        log.warn("Unknown query type - "+queryStringLog) ;
    }
    
    private String formatForLog(String queryString)
    {
        String tmp = queryString ;
        tmp = tmp.replace('\n', ' ') ;
        tmp = tmp.replace('\r', ' ') ;
        return tmp ;
    }
    
    private boolean datasetInProtocol(Request request)
    {
        String d = request.getParam(P_DEFAULT_GRAPH) ;
        if ( d != null && !d.equals("") )
            return true ;
        
        List n = request.getParams(P_NAMED_GRAPH) ;
        if ( n != null && n.size() > 0 )
            return true ;
        return false ;
    }
    
    protected Dataset datasetFromProtocol(Request request) throws QueryExecutionException
    {
        try {
            
            List graphURLs = request.getParams(P_DEFAULT_GRAPH) ;
            List namedGraphs = request.getParams(P_NAMED_GRAPH) ;
            
            graphURLs = removeEmptyValues(graphURLs) ;
            namedGraphs = removeEmptyValues(namedGraphs) ;
            
            if ( graphURLs.size() == 0 && namedGraphs.size() == 0 )
                return null ;
            
            Dataset dataset = DatasetFactory.create() ;
            // Look in cache for loaded graphs!!

            // ---- Default graph
            {
            	Model model = ModelFactory.createDefaultModel() ;
            	for ( String uri : graphURLs )
            	{
            		if ( uri == null )
            		{
            			log.warn("Null "+P_DEFAULT_GRAPH+ " (ignored)") ;
            			continue ;
            		}
            		if ( uri.equals("") )
            		{
            			log.warn("Empty "+P_DEFAULT_GRAPH+ " (ignored)") ;
            			continue ;
            		}

            		try {
            			GraphUtils.loadModel(model, uri, maxTriples) ;
            			log.info("Load (default) "+uri) ;
            		} catch (Exception ex)
            		{
            			log.info("Failed to load (default) "+uri+" : "+ex.getMessage()) ;
            			throw new QueryExecutionException(
            					ReturnCodes.rcArgumentUnreadable,
            					"Failed to load URL "+uri) ;
            		}
            	}
            	dataset.setDefaultModel(model) ;
            }
            // ---- Named graphs
            if ( namedGraphs != null )
            {
                for ( String uri : namedGraphs )
                {
                    if ( uri == null )
                    {
                        log.warn("Null "+P_NAMED_GRAPH+ " (ignored)") ;
                        continue ;
                    }
                    if ( uri.equals("") )
                    {
                        log.warn("Empty "+P_NAMED_GRAPH+ " (ignored)") ;
                        continue ;
                    }
                    try {
                        Model model2 = fileManager.loadModel(uri) ;
                        log.info("Load (named) "+uri) ;
                        dataset.addNamedModel(uri, model2) ;
                    } catch (Exception ex)
                    {
                        log.info("Failer to load (named) "+uri+" : "+ex.getMessage()) ;
                        throw new QueryExecutionException(
                                                          ReturnCodes.rcArgumentUnreadable,
                                                          "Failed to load URL "+uri) ;
                    }
                }
            }
            
            return dataset ;
            
        } 
        catch (QueryExecutionException ex) { throw ex ; }
        catch (Exception ex)
        {
            log.info("SPARQL parameter error",ex) ;
            throw new QueryExecutionException(
                                              ReturnCodes.rcArgumentError, "Parameter error");
        }
        
    }

    private List removeEmptyValues(List strList)
    {
        for ( Iterator iter = strList.iterator() ; iter.hasNext(); )
        {
            String v = iter.next();
            if ( v.equals("") ) 
                iter.remove() ;
        }
        return strList ; 
    }
    
    /**
     * @param queryURI
     * @return
     */
    private String getRemoteString(String queryURI)
    {
        return FileManager.get().readWholeFileAsUTF8(queryURI) ;
    }
}

/*
 * (c) Copyright 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */




© 2015 - 2025 Weber Informatics LLC | Privacy Policy