001    /**
002     * Copyright (C) 2007-2011, Jens Lehmann
003     *
004     * This file is part of DL-Learner.
005     *
006     * DL-Learner is free software; you can redistribute it and/or modify
007     * it under the terms of the GNU General Public License as published by
008     * the Free Software Foundation; either version 3 of the License, or
009     * (at your option) any later version.
010     *
011     * DL-Learner is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014     * GNU General Public License for more details.
015     *
016     * You should have received a copy of the GNU General Public License
017     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
018     */
019    
020    package org.dllearner.kb.sparql;
021    
022    import java.io.ByteArrayInputStream;
023    import java.io.ByteArrayOutputStream;
024    import java.io.File;
025    import java.io.IOException;
026    import java.io.UnsupportedEncodingException;
027    import java.nio.charset.Charset;
028    
029    import javax.xml.ws.http.HTTPException;
030    
031    import org.apache.log4j.Logger;
032    import org.dllearner.utilities.Files;
033    import org.dllearner.utilities.JamonMonitorLogger;
034    
035    import com.hp.hpl.jena.query.ResultSet;
036    import com.hp.hpl.jena.query.ResultSetFactory;
037    import com.hp.hpl.jena.query.ResultSetFormatter;
038    import com.hp.hpl.jena.query.ResultSetRewindable;
039    import com.hp.hpl.jena.sparql.engine.http.QueryEngineHTTP;
040    import com.jamonapi.Monitor;
041    
042    /**
043     * Represents one SPARQL query. It includes support for stopping the SPARQL
044     * query (which may be necessary if a timeout is reached) and is designed to be
045     * able to run a query in a separate thread. 
046     * 
047     * @author Jens Lehmann
048     * @author Sebastian Hellmann
049     * 
050     */
051    public class SparqlQuery {
052    
053            private static boolean logDeletedOnStart = false;
054    
055            private static Logger logger = Logger.getLogger(SparqlQuery.class);
056            
057            // additional file for logging SPARQL queries etc.
058            private static String sparqlLog = "log/sparql.txt";
059    
060            // whether the query is currently running
061            private boolean isRunning = false;
062    
063            // whether the query has been executed
064            private boolean wasExecuted = false;
065    
066            private String sparqlQueryString;
067    
068            private QueryEngineHTTP queryExecution;
069    
070            private SparqlEndpoint sparqlEndpoint;
071    
072            private ResultSetRewindable rs;
073    
074            /**
075             * Standard constructor.
076             * 
077             * @param sparqlQueryString
078             *            A SPARQL query string
079             * @param sparqlEndpoint
080             *            An Endpoint object
081             */
082            public SparqlQuery(String sparqlQueryString, SparqlEndpoint sparqlEndpoint) {
083                    // QUALITY there seems to be a bug in ontowiki
084                    this.sparqlQueryString = sparqlQueryString.replaceAll("\n", " ");
085                    this.sparqlEndpoint = sparqlEndpoint;
086            }
087    
088            /**
089             * Sends a SPARQL query using the Jena library.
090             * 
091             */
092            public ResultSetRewindable send() {
093                    isRunning = true;
094    
095                    String service = sparqlEndpoint.getURL().toString();
096    
097                    writeToSparqlLog("***********\nNew Query:");
098                    SparqlQuery.writeToSparqlLog("wget -S -O - '\n" + sparqlEndpoint.getHTTPRequest());
099                    writeToSparqlLog(sparqlQueryString);
100    
101                    queryExecution = new QueryEngineHTTP(service, sparqlQueryString);
102    
103                    // add default and named graphs
104                    for (String dgu : sparqlEndpoint.getDefaultGraphURIs()) {
105                            queryExecution.addDefaultGraph(dgu);
106                    }
107                    for (String ngu : sparqlEndpoint.getNamedGraphURIs()) {
108                            queryExecution.addNamedGraph(ngu);
109                    }
110    
111                    Monitor httpTime = JamonMonitorLogger.getTimeMonitor(SparqlQuery.class, "sparql query time").start();
112    
113                    try {
114                            logger.debug("sending query: length: " + sparqlQueryString.length() + " | ENDPOINT: "
115                                            + sparqlEndpoint.getURL().toString());
116                            
117                            // we execute the query and store the result in a rewindable result set
118                            ResultSet tmp = queryExecution.execSelect();
119                            rs = ResultSetFactory.makeRewindable(tmp);
120                    } catch (HTTPException e) {
121                            logger.debug("HTTPException in SparqlQuery\n" + e.toString());
122                            logger.debug("query was " + sparqlQueryString);
123                            writeToSparqlLog("ERROR: HTTPException occured" + e.toString());
124                            isRunning = false;
125                            throw e;
126                    // TODO: RuntimeException is very general; is it possible to catch more specific exceptions?
127                    } catch (RuntimeException e) {
128                            if (logger.isDebugEnabled()) {
129                                    logger.debug("RuntimeException in SparqlQuery (see /log/sparql.txt): "
130                                                    + e.toString());
131                                    int length = Math.min(sparqlQueryString.length(), 300);
132                                    logger.debug("query was (max. 300 chars displayed) "
133                                                    + sparqlQueryString.substring(0, length - 1).replaceAll("\n", " "));
134                            }
135                            writeToSparqlLog("ERROR: HTTPException occured: " + e.toString());
136                            isRunning = false;
137                            throw e;
138                    }
139    
140                    httpTime.stop();
141                    isRunning = false;
142                    wasExecuted = true;
143                    return rs;
144            }
145    
146            public boolean sendAsk() {
147                    isRunning = true;
148                    String service = sparqlEndpoint.getURL().toString();
149                    queryExecution = new QueryEngineHTTP(service, sparqlQueryString);
150                    boolean result = queryExecution.execAsk();
151                    isRunning = false;
152                    return result;
153            }
154            
155            /**
156             * Stops the execution of the query.
157             */
158            public void stop() {
159                    queryExecution.abort();
160                    isRunning = false;
161            }
162    
163            /**
164             * Gets the String representation of the SPARQL query.
165             * 
166             * @return sparqlQueryString
167             */
168            public String getSparqlQueryString() {
169                    return sparqlQueryString;
170            }
171    
172            /**
173             * @return sparqlEndpoint object
174             */
175            public SparqlEndpoint getSparqlEndpoint() {
176                    return sparqlEndpoint;
177            }
178    
179            /**
180             * 
181             * @return boolean
182             */
183            public boolean isRunning() {
184                    return isRunning;
185            }
186    
187            /**
188             * Return the result in JSON format.
189             * 
190             * @return A JSON string converted from the result set or null 
191             * if the query has not been executed.
192             */
193            public String getJson() {
194                    if(wasExecuted) {
195                            return convertResultSetToJSON(rs);
196                    } else {
197                            return null;
198                    }
199            }
200    
201            /**
202             * Converts the result set to an XML string.
203             * 
204             * @return An XML String
205             */
206            public String getXMLString() {
207                    if(wasExecuted) {
208                            return convertResultSetToXMLString(rs);
209                    } else {
210                            return null;
211                    }               
212            }
213    
214            /**
215             * Special log for debugging SPARQL query execution. It lives here:
216             * "log/sparql.txt" if the directory doesn't exist, there could be an error.
217             * 
218             * @param s
219             *            the String to log
220             */
221            private static void writeToSparqlLog(String s) {
222                    new File("log").mkdirs();
223                    File f = new File(sparqlLog);
224                    if(!f.canWrite() ){
225                            try {
226                                    f.createNewFile();
227                            } catch (IOException e) {
228                                    e.printStackTrace();
229                            }
230    //                      logger.info("could not write SPARQL log to : " + f.getAbsolutePath());
231    //                      return ;
232                    }       
233                    
234                    if (!logDeletedOnStart) {
235                                    Files.createFile(f, s + "\n");
236                                    logDeletedOnStart = true;
237                            } else {
238                                    Files.appendFile(f, s + "\n");
239                            }
240                    
241            }
242    
243            /**
244             * Converts Jena result set to XML. To make a ResultSet rewindable use:
245             * ResultSetRewindable rsRewind =
246             * ResultSetFactory.makeRewindable(resultSet);
247             * 
248             * @param resultSet
249             *            The result set to transform, must be rewindable to prevent
250             *            errors.
251             * @return String xml
252             */
253            public static String convertResultSetToXMLString(ResultSetRewindable resultSet) {
254                    String retVal = ResultSetFormatter.asXMLString(resultSet);
255                    resultSet.reset();
256                    return retVal;
257            }
258    
259            /**
260             * Converts Jena result set to JSON.
261             * 
262             * @param resultSet
263             *            The result set to transform, must be rewindable to prevent
264             *            errors.
265             * @return JSON representation of the result set.
266             */
267            public static String convertResultSetToJSON(ResultSetRewindable resultSet) {
268                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
269                    ResultSetFormatter.outputAsJSON(baos, resultSet);
270                    resultSet.reset();
271                    try {
272                            return baos.toString("UTF-8");
273                    } catch (UnsupportedEncodingException e) {
274                            // should never happen as UTF-8 is supported
275                            throw new Error(e);
276                    }
277            }
278    
279            /**
280             * Converts from JSON to internal Jena format.
281             * 
282             * @param json
283             *            A JSON representation if a SPARQL query result.
284             * @return A Jena ResultSet.
285             */
286            public static ResultSetRewindable convertJSONtoResultSet(String json) {
287                    ByteArrayInputStream bais = new ByteArrayInputStream(json
288                                    .getBytes(Charset.forName("UTF-8")));
289                    // System.out.println("JSON " + json);
290                    return ResultSetFactory.makeRewindable(ResultSetFactory.fromJSON(bais));
291            }
292    
293            /**
294             * Converts from JSON to xml format.
295             * 
296             * @param json
297             *            A JSON representation if a SPARQL query result.
298             * @return A Jena ResultSet.
299             */
300            public static String convertJSONtoXML(String json) {
301                    return convertResultSetToXMLString(convertJSONtoResultSet(json));
302            }
303    
304    }