Author: andy Date: Wed Nov 13 21:44:25 2013 New Revision: 1541741 URL: http://svn.apache.org/r1541741 Log: QueryValidator generating JSON response
Added: jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/QueryValidator.java jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationAction.java jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationError.java jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidatorBaseJson.java Added: jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/QueryValidator.java URL: http://svn.apache.org/viewvc/jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/QueryValidator.java?rev=1541741&view=auto ============================================================================== --- jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/QueryValidator.java (added) +++ jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/QueryValidator.java Wed Nov 13 21:44:25 2013 @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.validation2 ; + +import org.apache.jena.atlas.io.IndentedLineBuffer ; +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonObject ; + +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.query.QueryFactory ; +import com.hp.hpl.jena.query.QueryParseException ; +import com.hp.hpl.jena.query.Syntax ; +import com.hp.hpl.jena.sparql.algebra.Algebra ; +import com.hp.hpl.jena.sparql.algebra.Op ; +import com.hp.hpl.jena.sparql.serializer.SerializationContext ; + +public class QueryValidator extends ValidatorBaseJson { + public QueryValidator() {} + + @Override + protected String validatorName() { + return "SPARQL Query" ; + } + + static final String paramQuery = "query" ; + static final String paramSyntax = "languageSyntax" ; + + static final String jInput = "input" ; + + static final String jFormatted = "formatted" ; + static final String jAlgebra = "algebra" ; + static final String jAlgebraQuads = "algebra-quads" ; + static final String jAlgebraOpt = "algebra-opt" ; + static final String jAlgebraOptQuads = "algebra-opt-quads" ; + + static final String jParseError = "parse-error" ; + static final String jParseErrorLine = "parse-error-line" ; + static final String jParseErrorCol = "parse-error-column" ; + + @Override + protected JsonObject execute(ValidationAction action) { + JsonBuilder obj = new JsonBuilder() ; + obj.startObject() ; + + final String queryString = getArg(action, paramQuery) ; + + String querySyntax = getArgOrNull(action, paramSyntax) ; + if ( querySyntax == null || querySyntax.equals("") ) + querySyntax = "SPARQL" ; + + Syntax language = Syntax.lookup(querySyntax) ; + if ( language == null ) { + errorBadRequest("Unknown syntax: " + querySyntax) ; + return null ; + } + + boolean outputSPARQL = true ; + boolean outputAlgebra = true ; + boolean outputQuads = true ; + boolean outputOptimized = true ; + boolean outputOptimizedQuads = true ; + + obj.key(jInput).value(queryString) ; + + // Attempt to parse it. + Query query = null ; + try { + query = QueryFactory.create(queryString, "http://example/base/", language) ; + } catch (QueryParseException ex) { + obj.key(jParseError).value(ex.getMessage()) ; + obj.key(jParseErrorLine).value(ex.getLine()) ; + obj.key(jParseErrorCol).value(ex.getColumn()) ; + } + + if ( query != null ) { + + if ( outputSPARQL ) + formatted(obj, query) ; + + if ( outputAlgebra ) + algebra(obj, query) ; + + if ( outputQuads ) + algebraQuads(obj, query) ; + + if ( outputOptimized ) + algebraOpt(obj, query) ; + + if ( outputOptimizedQuads ) + algebraOptQuads(obj, query) ; + } + + obj.finishObject() ; + return obj.build().getAsObject() ; + } + + private void formatted(JsonBuilder obj, Query query) { + IndentedLineBuffer out = new IndentedLineBuffer() ; + query.serialize(out) ; + obj.key(jFormatted).value(out.asString()) ; + } + + private void algebra(JsonBuilder obj, Query query) { + Op op = Algebra.compile(query) ; + obj.key(jAlgebra).value(string(query, op)) ; + } + + private void algebraQuads(JsonBuilder obj, Query query) { + Op op = Algebra.compile(query) ; + op = Algebra.toQuadForm(op) ; + obj.key(jAlgebraQuads).value(string(query, op)) ; + } + + private void algebraOpt(JsonBuilder obj, Query query) { + Op op = Algebra.compile(query) ; + op = Algebra.optimize(op) ; + obj.key(jAlgebraOpt).value(string(query, op)) ; + } + + private void algebraOptQuads(JsonBuilder obj, Query query) { + Op op = Algebra.compile(query) ; + op = Algebra.toQuadForm(op) ; + op = Algebra.optimize(op) ; + obj.key(jAlgebraOptQuads).value(string(query, op)) ; + } + + private String string(Query query, Op op) { + final SerializationContext sCxt = new SerializationContext(query) ; + IndentedLineBuffer out = new IndentedLineBuffer() ; + op.output(out, sCxt) ; + return out.asString() ; + } +} Added: jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationAction.java URL: http://svn.apache.org/viewvc/jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationAction.java?rev=1541741&view=auto ============================================================================== --- jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationAction.java (added) +++ jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationAction.java Wed Nov 13 21:44:25 2013 @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.validation2; + +import java.util.HashMap ; +import java.util.Map ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.logging.Log ; + +public class ValidationAction +{ + public final boolean verbose ; + public final long id ; + private boolean startTimeIsSet = false ; + private boolean finishTimeIsSet = false ; + + private long startTime = -2 ; + private long finishTime = -2 ; + + // Outcome. + int statusCode = -1 ; + String message = null ; + int contentLength = -1 ; + String contentType = null ; + + Map <String, String> headers = new HashMap<String, String>() ; + public HttpServletRequest request; + public HttpServletResponse response ; + + public ValidationAction(long id, HttpServletRequest request, HttpServletResponse response, boolean verbose) { + this.id = id ; + this.request = request ; + this.response = response ; + this.verbose = false ; + } + + /** Reduce to a size that can be kept around for sometime */ + public void minimize() { + this.request = null ; + this.response = null ; + } + + public void setStartTime() { + if ( startTimeIsSet ) + Log.warn(this, "Start time reset") ; + startTimeIsSet = true ; + this.startTime = System.nanoTime() ; + } + + public void setFinishTime() { + if ( finishTimeIsSet ) + Log.warn(this, "Finish time reset") ; + finishTimeIsSet = true ; + this.finishTime = System.nanoTime() ; + } + + public HttpServletRequest getRequest() { return request ; } + + public HttpServletResponse getResponse() { return response ; } + + /** Return the recorded time taken in milliseconds. + * {@linkplain #setStartTime} and {@linkplain #setFinishTime} + * must have been called. + */ + public long getTime() + { + if ( ! startTimeIsSet ) + Log.warn(this, "Start time not set") ; + if ( ! finishTimeIsSet ) + Log.warn(this, "Finish time not set") ; + return (finishTime-startTime)/(1000*1000) ; + } +} Added: jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationError.java URL: http://svn.apache.org/viewvc/jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationError.java?rev=1541741&view=auto ============================================================================== --- jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationError.java (added) +++ jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidationError.java Wed Nov 13 21:44:25 2013 @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.validation2; + +public class ValidationError { + +} + Added: jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidatorBaseJson.java URL: http://svn.apache.org/viewvc/jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidatorBaseJson.java?rev=1541741&view=auto ============================================================================== --- jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidatorBaseJson.java (added) +++ jena/branches/jena-fuseki-new-ui/src/main/java/org/apache/jena/fuseki/validation2/ValidatorBaseJson.java Wed Nov 13 21:44:25 2013 @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.validation2; + +import static java.lang.String.format ; + +import java.io.OutputStream ; +import java.util.Enumeration ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JSON ; +import org.apache.jena.atlas.json.JsonObject ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.fuseki.servlets.ActionErrorException ; +import org.apache.jena.fuseki.servlets.ServletBase ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.web.HttpSC ; +import org.slf4j.Logger ; + +import com.hp.hpl.jena.query.ARQ ; +import com.hp.hpl.jena.sparql.util.Context ; + +/** ValidationBase for JSON out */ +public abstract class ValidatorBaseJson extends ServletBase +{ + protected static Logger serviceLog = Fuseki.requestLog ; + public static final String respService = "X-Service" ; + + @Override + public void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse) + { execute(httpRequest, httpResponse) ; } + + @Override + public void doPost(HttpServletRequest httpRequest, HttpServletResponse httpResponse) + { execute(httpRequest, httpResponse) ; } + + protected void execute(HttpServletRequest request, HttpServletResponse response) { + long id = allocRequestId(request, response) ; + ValidationAction action = new ValidationAction(id, request, response, false) ; + printRequest(action) ; + action.setStartTime() ; + + response = action.response ; + initResponse(request, response) ; + Context cxt = ARQ.getContext() ; + + try { + JsonObject obj = execute(action) ; + action.statusCode = HttpSC.OK_200 ; + action.message = "OK" ; + response.setCharacterEncoding(WebContent.charsetUTF8); + response.setContentType(WebContent.contentTypeJSON); + //response.setContentType(WebContent.contentTypeTextPlain); + action.response.setStatus(HttpSC.OK_200) ; + OutputStream out = response.getOutputStream() ; + JSON.write(out, obj); + } catch (ActionErrorException ex) { + if ( ex.exception != null ) + ex.exception.printStackTrace(System.err) ; + if ( ex.message != null ) + responseSendError(response, ex.rc, ex.message) ; + else + responseSendError(response, ex.rc) ; + //(null, string, statusCode) ; + } catch (Throwable th) { + responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, "Internal Error") ; + } + action.setFinishTime() ; + printResponse(action) ; + } + + private void initResponse(HttpServletRequest request, HttpServletResponse response) + { + setCommonHeaders(response) ; + String method = request.getMethod() ; + // All GET and HEAD operations are sensitive to conneg so ... + if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) || HttpNames.METHOD_HEAD.equalsIgnoreCase(method) ) + setVaryHeader(response) ; + } + + private void printRequest(ValidationAction action) + { + String url = wholeRequestURL(action.request) ; + String method = action.request.getMethod() ; + + log.info(format("[%d] %s %s", action.id, method, url)) ; + if ( action.verbose ) { + Enumeration<String> en = action.request.getHeaderNames() ; + for (; en.hasMoreElements();) { + String h = en.nextElement() ; + Enumeration<String> vals = action.request.getHeaders(h) ; + if (!vals.hasMoreElements()) + log.info(format("[%d] ", action.id, h)) ; + else { + for (; vals.hasMoreElements();) + log.info(format("[%d] %-20s %s", action.id, h, vals.nextElement())) ; + } + } + } + } + + private void printResponse(ValidationAction action) + { + long time = action.getTime() ; + + HttpServletResponse response = action.response ; + if ( action.verbose ) + { +// if ( action.contentType != null ) +// log.info(format("[%d] %-20s %s", action.id, HttpNames.hContentType, action.contentType)) ; +// if ( action.contentLength != -1 ) +// log.info(format("[%d] %-20s %d", action.id, HttpNames.hContentLengh, action.contentLength)) ; +// for ( Map.Entry<String, String> e: action.headers.entrySet() ) +// log.info(format("[%d] %-20s %s", action.id, e.getKey(), e.getValue())) ; + } + + String timeStr = fmtMillis(time) ; + + if ( action.message == null ) + log.info(String.format("[%d] %d %s (%s) ", action.id, action.statusCode, HttpSC.getMessage(action.statusCode), timeStr)) ; + else + log.info(String.format("[%d] %d %s (%s) ", action.id, action.statusCode, action.message, timeStr)) ; + } + + private static String fmtMillis(long time) + { + // Millis only? seconds only? + if ( time < 1000 ) + return String.format("%,d ms", time) ; + return String.format("%,.3f s", time/1000.0) ; + } + + protected abstract JsonObject execute(ValidationAction action) ; + + protected abstract String validatorName() ; + + protected void setHeaders(HttpServletResponse httpResponse) + { + httpResponse.setCharacterEncoding(WebContent.charsetUTF8) ; + httpResponse.setContentType(WebContent.contentTypeJSON) ; + httpResponse.setHeader(respService, "Jena Fuseki Validator / "+validatorName()+": http://jena.apache.org/") ; + } + + protected static String getArg(ValidationAction action, String paramName) { + String arg = getArgOrNull(action, paramName) ; + if ( arg == null ) { + error(HttpSC.BAD_REQUEST_400, "No parameter given: " + paramName) ; + return null ; + } + return arg ; + } + + protected static String getArgOrNull(ValidationAction action, String paramName) { + String[] args = getArgs(action, paramName) ; + + if ( args == null || args.length == 0 ) + return null ; + + if ( args.length > 1 ) { + error(HttpSC.BAD_REQUEST_400, "Too many ("+args.length+") parameter values: "+paramName) ; + return null ; + } + + return args[0] ; + } + + protected static String[] getArgs(ValidationAction action, String paramName) { + String[] args = action.request.getParameterValues(paramName) ; + if ( args == null || args.length == 0 ) + return null ; + return args ; + } +} +