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