http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java new file mode 100644 index 0000000..32a65e6 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java @@ -0,0 +1,231 @@ +/* + * 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.servlets; + +import static java.lang.String.format ; + +import java.io.IOException ; +import java.io.InputStream ; +import java.util.Map ; +import java.util.Map.Entry ; + +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.atlas.web.ContentType ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFLanguages ; +import org.apache.jena.riot.RiotException ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.riot.system.StreamRDF ; +import org.apache.jena.riot.system.StreamRDFLib ; +import org.apache.jena.web.HttpSC ; + +import com.hp.hpl.jena.graph.Graph ; +import com.hp.hpl.jena.sparql.graph.GraphFactory ; + +/** The WRITE operations added to the READ operations */ +public class SPARQL_REST_RW extends SPARQL_REST_R +{ + public SPARQL_REST_RW() + { super() ; } + + @Override + protected void doOptions(HttpAction action) + { + action.response.setHeader(HttpNames.hAllow, "GET,HEAD,OPTIONS,PUT,DELETE,POST"); + action.response.setHeader(HttpNames.hContentLengh, "0") ; + success(action) ; + } + + @Override + protected void doDelete(HttpAction action) + { + action.beginWrite() ; + try { + Target target = determineTarget(action) ; + if ( log.isDebugEnabled() ) + log.debug("DELETE->"+target) ; + boolean existedBefore = target.exists() ; + if ( ! existedBefore) + { + // commit, not abort, because locking "transactions" don't support abort. + action.commit() ; + errorNotFound("No such graph: "+target.name) ; + } + deleteGraph(action) ; + action.commit() ; + } + finally { action.endWrite() ; } + ServletBase.successNoContent(action) ; + } + + @Override + protected void doPut(HttpAction action) { doPutPost(action, true) ; } + + @Override + protected void doPost(HttpAction action) { doPutPost(action, false) ; } + + private void doPutPost(HttpAction action, boolean overwrite) { + ContentType ct = FusekiLib.getContentType(action) ; + if ( ct == null ) + errorBadRequest("No Content-Type:") ; + + // Helper case - if it's a possible HTTP file upload, pretend that's the action. + if ( WebContent.contentTypeMultipartFormData.equalsIgnoreCase(ct.getContentType()) ) { + String base = wholeRequestURL(action.request) ; + SPARQL_Upload.upload(action, base) ; + return ; + } + + if ( WebContent.matchContentType(WebContent.ctMultipartMixed, ct) ) + error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "multipart/mixed not supported") ; + + boolean existedBefore = false ; + if ( action.isTransactional() ) + existedBefore = addDataIntoTxn(action, overwrite) ; + else + existedBefore = addDataIntoNonTxn(action, overwrite) ; + + if ( existedBefore ) + ServletBase.successNoContent(action) ; + else + ServletBase.successCreated(action) ; + } + + /** Directly add data in a transaction. + * Assumes recovery from parse errors by transaction abort. + * Return whether the target existed before. + * @param action + * @param cleanDest Whether to remove data first (true = PUT, false = POST) + * @return whether the target existed beforehand + */ + protected static boolean addDataIntoTxn(HttpAction action, boolean overwrite) { + action.beginWrite(); + Target target = determineTarget(action) ; + boolean existedBefore = false ; + try { + if ( log.isDebugEnabled() ) + log.debug(" ->"+target) ; + existedBefore = target.exists() ; + Graph g = target.graph() ; + if ( overwrite && existedBefore ) + clearGraph(target) ; + StreamRDF sink = StreamRDFLib.graph(g) ; + incomingData(action, sink); + action.commit() ; + return existedBefore ; + } catch (RiotException ex) { + // Parse error + action.abort() ; + errorBadRequest(ex.getMessage()) ; + return existedBefore ; + } catch (Exception ex) { + // Something else went wrong. Backout. + action.abort() ; + errorOccurred(ex.getMessage()) ; + return existedBefore ; + } finally { + action.endWrite() ; + } + } + + /** Add data where the destination does not support full transactions. + * In particular, with no abort, and actions probably going to the real storage + * parse errors can lead to partial updates. Instead, parse to a temporary + * graph, then insert that data. + * @param action + * @param cleanDest Whether to remove data first (true = PUT, false = POST) + * @return whether the target existed beforehand. + */ + + protected static boolean addDataIntoNonTxn(HttpAction action, boolean overwrite) { + Graph graphTmp = GraphFactory.createGraphMem() ; + StreamRDF dest = StreamRDFLib.graph(graphTmp) ; + + try { incomingData(action, dest); } + catch (RiotException ex) { + errorBadRequest(ex.getMessage()) ; + return false ; + } + // Now insert into dataset + action.beginWrite() ; + Target target = determineTarget(action) ; + boolean existedBefore = false ; + try { + if ( log.isDebugEnabled() ) + log.debug(" ->"+target) ; + existedBefore = target.exists() ; + if ( overwrite && existedBefore ) + clearGraph(target) ; + FusekiLib.addDataInto(graphTmp, target.dsg, target.graphName) ; + action.commit() ; + return existedBefore ; + } catch (Exception ex) { + // We parsed into a temporary graph so an exception at this point + // is not because of a parse error. + // We're in the non-transactional branch, this probably will not work + // but it might and there is no harm safely trying. + try { action.abort() ; } catch (Exception ex2) {} + errorOccurred(ex.getMessage()) ; + return existedBefore ; + } finally { action.endWrite() ; } + } + + private static void incomingData(HttpAction action, StreamRDF dest) { + String base = wholeRequestURL(action.request) ; + ContentType ct = FusekiLib.getContentType(action) ; + Lang lang = RDFLanguages.contentTypeToLang(ct.getContentType()) ; + if ( lang == null ) { + errorBadRequest("Unknown content type for triples: " + ct) ; + return ; + } + InputStream input = null ; + try { input = action.request.getInputStream() ; } + catch (IOException ex) { IO.exception(ex) ; } + + int len = action.request.getContentLength() ; + if ( action.verbose ) { + if ( len >= 0 ) + log.info(format("[%d] Body: Content-Length=%d, Content-Type=%s, Charset=%s => %s", action.id, len, + ct.getContentType(), ct.getCharset(), lang.getName())) ; + else + log.info(format("[%d] Body: Content-Type=%s, Charset=%s => %s", action.id, ct.getContentType(), + ct.getCharset(), lang.getName())) ; + } + + parse(action, dest, input, lang, base) ; + } + + protected static void deleteGraph(HttpAction action) { + Target target = determineTarget(action) ; + if ( target.isDefault ) + target.graph().clear() ; + else + action.getActiveDSG().removeGraph(target.graphName) ; + } + + protected static void clearGraph(Target target) { + Graph g = target.graph() ; + g.clear() ; + Map<String, String> pm = g.getPrefixMapping().getNsPrefixMap() ; + for ( Entry<String, String> e : pm.entrySet() ) + g.getPrefixMapping().removeNsPrefix(e.getKey()) ; + } +}
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java new file mode 100644 index 0000000..a3d5271 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java @@ -0,0 +1,458 @@ +/* + * 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.servlets; + +import static java.lang.String.format ; +import static org.apache.jena.fuseki.server.CounterName.Requests ; +import static org.apache.jena.fuseki.server.CounterName.RequestsBad ; +import static org.apache.jena.fuseki.server.CounterName.RequestsGood ; + +import java.io.IOException ; +import java.util.Enumeration ; +import java.util.Map ; +import java.util.concurrent.atomic.AtomicLong ; + +import javax.servlet.ServletException ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.RuntimeIOException ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.fuseki.server.* ; +import org.apache.jena.web.HttpSC ; + +import com.hp.hpl.jena.query.ARQ ; +import com.hp.hpl.jena.query.QueryCancelledException ; +import com.hp.hpl.jena.sparql.util.Context ; + +/** + * Base servlet for SPARQL requests. + */ +public abstract class SPARQL_ServletBase extends ServletBase +{ + /** + * Creates a new SPARQL base Servlet. + */ + protected SPARQL_ServletBase() { super() ; } + + // Common framework for handling HTTP requests + /** + * Handles GET and POST requests. + * @param request HTTP request + * @param response HTTP response + */ + protected void doCommon(HttpServletRequest request, HttpServletResponse response) + //throws ServletException, IOException + { + try { + long id = allocRequestId(request, response); + + // Lifecycle + HttpAction action = allocHttpAction(id, request, response) ; + // then add to doCommonWorker + // work with HttpServletResponseTracker + + printRequest(action) ; + action.setStartTime() ; + + response = action.response ; + initResponse(request, response) ; + Context cxt = ARQ.getContext() ; + + try { + execCommonWorker(action) ; + } catch (QueryCancelledException ex) { + // Also need the per query info ... + String message = String.format("The query timed out (restricted to %s ms)", cxt.get(ARQ.queryTimeout)); + // Possibility :: response.setHeader("Retry-after", "600") ; // 5 minutes + responseSendError(response, HttpSC.SERVICE_UNAVAILABLE_503, message); + } catch (ActionErrorException ex) { + if ( ex.exception != null ) + ex.exception.printStackTrace(System.err) ; + // Log message done by printResponse in a moment. + if ( ex.message != null ) + responseSendError(response, ex.rc, ex.message) ; + else + responseSendError(response, ex.rc) ; + } catch (RuntimeIOException ex) { + log.warn(format("[%d] Runtime IO Exception (client left?) RC = %d", id, HttpSC.INTERNAL_SERVER_ERROR_500)) ; + responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ; + } catch (Throwable ex) { + // This should not happen. + //ex.printStackTrace(System.err) ; + log.warn(format("[%d] RC = %d : %s", id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()), ex) ; + responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ; + } + + action.setFinishTime() ; + printResponse(action) ; + archiveHttpAction(action) ; + } catch (Throwable th) { + log.error("Internal error", th) ; + } + } + + // ---- Operation lifecycle + + /** + * Returns a fresh HTTP Action for this request. + * @param id the Request ID + * @param request HTTP request + * @param response HTTP response + * @return a new HTTP Action + */ + protected HttpAction allocHttpAction(long id, HttpServletRequest request, HttpServletResponse response) { + // Need a way to set verbose logging on a per servlet and per request basis. + return new HttpAction(id, request, response, verboseLogging) ; + } + + /** + * Validates a HTTP Action. + * @param action HTTP Action + */ + protected abstract void validate(HttpAction action) ; + + /** + * Performs the HTTP Action. + * @param action HTTP Action + */ + protected abstract void perform(HttpAction action) ; + + /** + * Default start step. + * @param action HTTP Action + */ + protected void startRequest(HttpAction action) { + } + + /** + * Default finish step. + * @param action HTTP Action + */ + protected void finishRequest(HttpAction action) { } + + /** + * Archives the HTTP Action. + * @param action HTTP Action + * @see HttpAction#minimize() + */ + private void archiveHttpAction(HttpAction action) + { + action.minimize() ; + } + + /** + * Executes common tasks, including mapping the request to the right dataset, setting the dataset into the HTTP + * action, and retrieving the service for the dataset requested. Finally, it calls the + * {@link #executeAction(HttpAction)} method, which executes the HTTP Action life cycle. + * @param action HTTP Action + */ + private void execCommonWorker(HttpAction action) + { + DatasetRef dsRef = null ; + String uri = action.request.getRequestURI() ; + + String datasetUri = mapRequestToDataset(uri) ; + + if ( datasetUri != null ) { + dsRef = DatasetRegistry.get().get(datasetUri) ; + if ( dsRef == null ) { + errorNotFound("No dataset for URI: "+datasetUri) ; + return ; + } + } else + dsRef = FusekiConfig.serviceOnlyDatasetRef() ; + + action.setDataset(dsRef) ; + String serviceName = mapRequestToService(dsRef, uri, datasetUri) ; + ServiceRef srvRef = dsRef.getServiceRef(serviceName) ; + action.setService(srvRef) ; + executeAction(action) ; + } + + /** + * Utility method, that increments and returns the AtomicLong value. + * @param x AtomicLong + */ + protected void inc(AtomicLong x) + { + x.incrementAndGet() ; + } + + /** + * Executes the HTTP Action. Serves as intercept point for the UberServlet. + * @param action HTTP Action + */ + protected void executeAction(HttpAction action) + { + executeLifecycle(action) ; + } + + /** + * Handles the service request lifecycle. Called directly by the UberServlet, + * which has not done any stats by this point. + * @param action {@link HttpAction} + * @see HttpAction + */ + protected void executeLifecycle(HttpAction action) + { + incCounter(action.dsRef, Requests) ; + incCounter(action.srvRef, Requests) ; + + startRequest(action) ; + try { + validate(action) ; + } catch (ActionErrorException ex) { + incCounter(action.dsRef,RequestsBad) ; + throw ex ; + } + + try { + perform(action) ; + // Success + incCounter(action.srvRef, RequestsGood) ; + incCounter(action.dsRef, RequestsGood) ; + } catch (ActionErrorException ex) { + incCounter(action.srvRef, RequestsBad) ; + incCounter(action.dsRef, RequestsBad) ; + throw ex ; + } catch (QueryCancelledException ex) { + incCounter(action.srvRef, RequestsBad) ; + incCounter(action.dsRef, RequestsBad) ; + throw ex ; + } finally { + finishRequest(action) ; + } + } + + /** + * Increments a counter. + * @param counters a {@link Counter} + * @param name a {@link CounterName} + */ + protected static void incCounter(Counters counters, CounterName name) { + try { + if ( counters.getCounters().contains(name) ) + counters.getCounters().inc(name) ; + } catch (Exception ex) { + Fuseki.serverLog.warn("Exception on counter inc", ex) ; + } + } + + /** + * Decrements a counter. + * @param counters a {@link Counter} + * @param name a {@link CounterName} + */ + protected static void decCounter(Counters counters, CounterName name) { + try { + if ( counters.getCounters().contains(name) ) + counters.getCounters().dec(name) ; + } catch (Exception ex) { + Fuseki.serverLog.warn("Exception on counter dec", ex) ; + } + } + + /** + * <p>Sends an <strong>error</strong> when the PATCH method is called.</p> + * <p>Throws ServletException or IOException as per overloaded method signature.</p> + * @param request HTTP request + * @param response HTTP response + * @throws ServletException from overloaded method signature + * @throws IOException from overloaded method signature + */ + protected void doPatch(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "HTTP PATCH not supported"); + } + + /** + * Prints the HTTP Action request to the program log, using the INFO level. + * @param action {@link HttpAction} + */ + private void printRequest(HttpAction 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())) ; + } + } + } + } + + /** + * Initiates the response, by setting common headers such as Access-Control-Allow-Origin and Server, and + * the Vary header if the request method used was a GET. + * @param request HTTP request + * @param response HTTP response + */ + 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) ; + } + + /** + * Prints the HTTP Action response to the program log, using the INFO level. + * @param action {@link HttpAction} + */ + private void printResponse(HttpAction action) + { + long time = action.getTime() ; + + HttpServletResponseTracker 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)) ; + } + + /** + * <p>Given a time epoch, it will return the time in milli seconds if it is less than 1000, + * otherwise it will normalize it to display as second.</p> + * <p>It appends a 'ms' suffix when using milli seconds, and ditto <i>s</i> for seconds.</p> + * <p>For instance: </p> + * <ul> + * <li>10 emits 10 ms</li> + * <li>999 emits 999 ms</li> + * <li>1000 emits 1.000000 s</li> + * <li>10000 emits 10.000000 s</li> + * </ul> + * @param time the time epoch + * @return the time in milli seconds or in seconds + */ + 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) ; + } + + /** + * Map request to uri in the registry. null means no mapping done (passthrough). + * @param uri the URI + * @return the dataset + */ + protected String mapRequestToDataset(String uri) + { + return mapRequestToDataset$(uri) ; + } + + /** + * A possible implementation for mapRequestToDataset(String) that assumes the form /dataset/service. + * @param uri the URI + * @return the dataset + */ + protected static String mapRequestToDataset$(String uri) + { + // Chop off trailing part - the service selector + // e.g. /dataset/sparql => /dataset + int i = uri.lastIndexOf('/') ; + if ( i == -1 ) + return null ; + if ( i == 0 ) + { + // started with '/' - leave. + return uri ; + } + + return uri.substring(0, i) ; + } + + /** + * Maps a request to a service (e.g. Query, Update). + * @param dsRef a {@link DatasetRef} + * @param uri the URI + * @param datasetURI the dataset URI + * @return an empty String (i.e. "") if the DatasetRef is null, or if its name is longer than the URI's name. + * Otherwise will return the service name. + */ + protected String mapRequestToService(DatasetRef dsRef, String uri, String datasetURI) + { + if ( dsRef == null ) + return "" ; + if ( dsRef.name.length() >= uri.length() ) + return "" ; + return uri.substring(dsRef.name.length()+1) ; // Skip the separating "/" + + } + + /** + * Implementation of mapRequestToDataset(String) that looks for the longest match in the registry. + * This includes use in direct naming GSP. + * @param uri the URI + * @return <code>null</code> if the URI is null, otherwise will return the longest match in the registry. + */ + protected static String mapRequestToDatasetLongest$(String uri) + { + if ( uri == null ) + return null ; + + // This covers local, using the URI as a direct name for + // a graph, not just using the indirect ?graph= or ?default + // forms. + + String ds = null ; + for ( String ds2 : DatasetRegistry.get().keys() ) { + if ( ! uri.startsWith(ds2) ) + continue ; + + if ( ds == null ) + { + ds = ds2 ; + continue ; + } + if ( ds.length() < ds2.length() ) + { + ds = ds2 ; + continue ; + } + } + return ds ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java new file mode 100644 index 0000000..0c10cee --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java @@ -0,0 +1,338 @@ +/** + * 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.servlets; + +import static java.lang.String.format ; + +import java.util.List ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.web.MediaType ; +import org.apache.jena.fuseki.DEF ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.fuseki.conneg.ConNeg ; +import org.apache.jena.fuseki.server.DatasetRef ; +import org.apache.jena.fuseki.server.ServiceRef ; +import org.apache.jena.riot.WebContent ; + +/** This servlet can be attached to a dataset location + * and acts as a router for all SPARQL operations + * (query, update, graph store, both direct and indirect naming). + */ +public abstract class SPARQL_UberServlet extends SPARQL_ServletBase +{ + protected abstract boolean allowQuery(HttpAction action) ; + protected abstract boolean allowUpdate(HttpAction action) ; + protected abstract boolean allowREST_R(HttpAction action) ; + protected abstract boolean allowREST_W(HttpAction action) ; + protected abstract boolean allowQuadsR(HttpAction action) ; + protected abstract boolean allowQuadsW(HttpAction action) ; + + public static class ReadOnly extends SPARQL_UberServlet + { + public ReadOnly() { super() ; } + @Override protected boolean allowQuery(HttpAction action) { return true ; } + @Override protected boolean allowUpdate(HttpAction action) { return false ; } + @Override protected boolean allowREST_R(HttpAction action) { return true ; } + @Override protected boolean allowREST_W(HttpAction action) { return false ; } + @Override protected boolean allowQuadsR(HttpAction action) { return true ; } + @Override protected boolean allowQuadsW(HttpAction action) { return false ; } + } + + public static class ReadWrite extends SPARQL_UberServlet + { + public ReadWrite() { super() ; } + @Override protected boolean allowQuery(HttpAction action) { return true ; } + @Override protected boolean allowUpdate(HttpAction action) { return true ; } + @Override protected boolean allowREST_R(HttpAction action) { return true ; } + @Override protected boolean allowREST_W(HttpAction action) { return true ; } + @Override protected boolean allowQuadsR(HttpAction action) { return true ; } + @Override protected boolean allowQuadsW(HttpAction action) { return true ; } + } + + public static class AccessByConfig extends SPARQL_UberServlet + { + public AccessByConfig() { super() ; } + @Override protected boolean allowQuery(HttpAction action) { return isEnabled(action.dsRef.query) ; } + @Override protected boolean allowUpdate(HttpAction action) { return isEnabled(action.dsRef.update) ; } + @Override protected boolean allowREST_R(HttpAction action) { return isEnabled(action.dsRef.readGraphStore) || allowREST_W(action); } + @Override protected boolean allowREST_W(HttpAction action) { return isEnabled(action.dsRef.readWriteGraphStore) ; } + // Quad operations tied to presence/absence of GSP. + @Override protected boolean allowQuadsR(HttpAction action) { return isEnabled(action.dsRef.readGraphStore) ; } + @Override protected boolean allowQuadsW(HttpAction action) { return isEnabled(action.dsRef.readWriteGraphStore) ; } + + private boolean isEnabled(ServiceRef service) { return service.isActive() ; } + } + + /* This can be used for a single servlet for everything (über-servlet) + * + * It can check for a request that looks like a service request and passes it on. + * This takes precedence over direct naming. + */ + + // Refactor? Extract the direct naming handling. + // To test: enable in SPARQLServer.configureOneDataset + + private final SPARQL_ServletBase queryServlet = new SPARQL_QueryDataset() ; + private final SPARQL_ServletBase updateServlet = new SPARQL_Update() ; + private final SPARQL_ServletBase uploadServlet = new SPARQL_Upload() ; + private final SPARQL_REST restServlet_RW = new SPARQL_REST_RW() ; + private final SPARQL_REST restServlet_R = new SPARQL_REST_R() ; + private final SPARQL_ServletBase restQuads = new REST_Quads() ; + + public SPARQL_UberServlet() { super(); } + + private String getEPName(String dsname, List<String> endpoints) + { + if (endpoints == null || endpoints.size() == 0) return null ; + String x = endpoints.get(0) ; + if ( ! dsname.endsWith("/") ) + x = dsname+"/"+x ; + else + x = dsname+x ; + return x ; + } + + // These calls should not happen because we hook in at executeAction + @Override protected void validate(HttpAction action) { throw new FusekiException("Call to SPARQL_UberServlet.validate") ; } + @Override protected void perform(HttpAction action) { throw new FusekiException("Call to SPARQL_UberServlet.perform") ; } + + /** Map request to uri in the registry. + * null means no mapping done + */ + @Override + protected String mapRequestToDataset(String uri) + { + return mapRequestToDatasetLongest$(uri) ; + } + + + /** Intercept the processing cycle at the point where the action has been set up, + * the dataset target decided but no validation or execution has been done, + * nor any stats have been done. + */ + @Override + protected void executeAction(HttpAction action) + { + long id = action.id ; + HttpServletRequest request = action.request ; + HttpServletResponse response = action.response ; + String uri = request.getRequestURI() ; + String method = request.getMethod() ; + DatasetRef desc = action.dsRef ; + + String trailing = findTrailing(uri, desc.name) ; + String qs = request.getQueryString() ; + + boolean hasParams = request.getParameterMap().size() > 0 ; + + // Test for parameters - includes HTML forms. + boolean hasParamQuery = request.getParameter(HttpNames.paramQuery) != null ; + // Include old name "request=" + boolean hasParamUpdate = request.getParameter(HttpNames.paramUpdate) != null || request.getParameter(HttpNames.paramRequest) != null ; + boolean hasParamGraph = request.getParameter(HttpNames.paramGraph) != null ; + boolean hasParamGraphDefault = request.getParameter(HttpNames.paramGraphDefault) != null ; + boolean isForm = WebContent.contentTypeHTMLForm.equalsIgnoreCase(request.getContentType()) ; + + String ct = request.getContentType() ; + String charset = request.getCharacterEncoding() ; + + MediaType mt = null ; + if ( ct != null ) + mt = MediaType.create(ct, charset) ; + + log.info(format("[%d] All: %s %s :: '%s' :: %s ? %s", id, method, desc.name, trailing, (mt==null?"<none>":mt), (qs==null?"":qs))) ; + + boolean hasTrailing = ( trailing.length() != 0 ) ; + + if ( ! hasTrailing && ! hasParams ) + { + restQuads.executeLifecycle(action) ; + return ; + } + + if ( ! hasTrailing ) + { + // Has params of some kind. + if ( hasParamQuery || WebContent.contentTypeSPARQLQuery.equalsIgnoreCase(ct) ) + { + // SPARQL Query + if ( ! allowQuery(action)) + errorForbidden("Forbidden: SPARQL query") ; + executeRequest(action, queryServlet, desc.query) ; + return ; + } + + if ( hasParamUpdate || WebContent.contentTypeSPARQLUpdate.equalsIgnoreCase(ct) ) + { + // SPARQL Update + if ( ! allowQuery(action)) + errorForbidden("Forbidden: SPARQL query") ; + executeRequest(action, updateServlet, desc.update) ; + return ; + } + + if ( hasParamGraph || hasParamGraphDefault ) + { + doGraphStoreProtocol(action) ; + return ; + } + + errorBadRequest("Malformed request") ; + errorForbidden("Forbidden: SPARQL Graph Store Protocol : Read operation : "+method) ; + } + + final boolean checkForPossibleService = true ; + if ( checkForPossibleService ) + { + // There is a trailing part. + // Check it's not the same name as a registered service. + // If so, dispatch to that service. + if ( serviceDispatch(action, desc.query, trailing, queryServlet) ) return ; + if ( serviceDispatch(action, desc.update, trailing, updateServlet) ) return ; + if ( serviceDispatch(action, desc.upload, trailing, uploadServlet) ) return ; + if ( serviceDispatch(action, desc.readGraphStore, trailing, restServlet_R) ) return ; + if ( serviceDispatch(action, desc.readWriteGraphStore, trailing, restServlet_RW) ) return ; + } + // There is a trailing part - params are illegal by this point. + if ( hasParams ) + // ?? Revisit to include query-on-one-graph + //errorBadRequest("Can't invoke a query-string service on a direct named graph") ; + errorNotFound("Not found: dataset='"+printName(desc.name)+"' service='"+printName(trailing)+"'"); + + // There is a trailing part - not a service, no params ==> GSP direct naming. + doGraphStoreProtocol(action) ; + } + + private String printName(String x) { + if ( x.startsWith("/") ) + return x.substring(1) ; + return x ; + } + + private void doGraphStoreProtocol(HttpAction action) + { + // The GSP servlets handle direct and indirect naming. + DatasetRef desc = action.dsRef ; + String method = action.request.getMethod() ; + + if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) || + HttpNames.METHOD_HEAD.equalsIgnoreCase(method) ) + { + if ( ! allowREST_R(action)) + // Graphs Store Protocol, indirect naming, read + // Indirect naming. Prefer the R service if available. + if ( desc.readGraphStore.isActive() ) + executeRequest(action, restServlet_R, desc.readGraphStore) ; + else if ( desc.readWriteGraphStore.isActive() ) + executeRequest(action, restServlet_RW, desc.readWriteGraphStore) ; + else + errorMethodNotAllowed(method) ; + return ; + } + + // Graphs Store Protocol, indirect naming, write + if ( ! allowREST_W(action)) + errorForbidden("Forbidden: SPARQL Graph Store Protocol : Write operation : "+method) ; + executeRequest(action, restServlet_RW, desc.readWriteGraphStore) ; + return ; + } + + private void executeRequest(HttpAction action, SPARQL_ServletBase servlet, ServiceRef service) + { + if ( service.endpoints.size() == 0 ) + errorMethodNotAllowed(action.request.getMethod()) ; + servlet.executeLifecycle(action) ; + } + + private void executeRequest(HttpAction action,SPARQL_ServletBase servlet) + { + servlet.executeLifecycle(action) ; +// // Forwarded dispatch. +// try +// { +// String target = getEPName(desc.name, endpointList) ; +// if ( target == null ) +// errorMethodNotAllowed(request.getMethod()) ; +// // ** relative servlet forward +// request.getRequestDispatcher(target).forward(request, response) ; + + +// // ** absolute srvlet forward +// // getServletContext().getRequestDispatcher(target) ; +// } catch (Exception e) { errorOccurred(e) ; } + } + + protected static MediaType contentNegotationQuads(HttpAction action) + { + MediaType mt = ConNeg.chooseContentType(action.request, DEF.quadsOffer, DEF.acceptNQuads) ; + if ( mt == null ) + return null ; + if ( mt.getContentType() != null ) + action.response.setContentType(mt.getContentType()); + if ( mt.getCharset() != null ) + action.response.setCharacterEncoding(mt.getCharset()) ; + return mt ; + } + + /** return true if dispatched */ + private boolean serviceDispatch(HttpAction action, ServiceRef service, String srvName , SPARQL_ServletBase servlet) + { + if ( ! service.endpoints.contains(srvName) ) + return false ; + servlet.executeLifecycle(action) ; + return true ; + } + + /** Find the graph (direct naming) or service name */ + protected String findTrailing(String uri, String dsname) + { + if ( dsname.length() >= uri.length() ) + return "" ; + return uri.substring(dsname.length()+1) ; // Skip the separating "/" + } + + @Override + protected void doHead(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doOptions(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doPut(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } + + @Override + protected void doDelete(HttpServletRequest request, HttpServletResponse response) + { doCommon(request, response) ; } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java new file mode 100644 index 0000000..a54715c --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java @@ -0,0 +1,307 @@ +/* + * 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.servlets; + +import static java.lang.String.format ; +import static org.apache.jena.fuseki.Fuseki.requestLog ; +import static org.apache.jena.fuseki.HttpNames.paramRequest ; +import static org.apache.jena.fuseki.HttpNames.paramUpdate ; +import static org.apache.jena.fuseki.HttpNames.paramUsingGraphURI ; +import static org.apache.jena.fuseki.HttpNames.paramUsingNamedGraphURI ; +import static org.apache.jena.fuseki.server.CounterName.UpdateExecErrors ; + +import java.io.ByteArrayInputStream ; +import java.io.IOException ; +import java.io.InputStream ; +import java.util.Arrays ; +import java.util.Collection ; +import java.util.Enumeration ; +import java.util.List ; + +import javax.servlet.ServletException ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.atlas.lib.StrUtils ; +import org.apache.jena.atlas.web.ContentType ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.iri.IRI ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.riot.system.IRIResolver ; +import org.apache.jena.web.HttpSC ; + +import com.hp.hpl.jena.graph.Node ; +import com.hp.hpl.jena.graph.NodeFactory ; +import com.hp.hpl.jena.query.QueryParseException ; +import com.hp.hpl.jena.query.Syntax ; +import com.hp.hpl.jena.sparql.modify.UsingList ; +import com.hp.hpl.jena.update.UpdateAction ; +import com.hp.hpl.jena.update.UpdateException ; +import com.hp.hpl.jena.update.UpdateFactory ; +import com.hp.hpl.jena.update.UpdateRequest ; + +public class SPARQL_Update extends SPARQL_Protocol +{ + // Base URI used to isolate parsing from the current directory of the server. + private static final String UpdateParseBase = "http://example/update-base/" ; + private static final IRIResolver resolver = IRIResolver.create(UpdateParseBase) ; + + public SPARQL_Update() + { super() ; } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.sendError(HttpSC.BAD_REQUEST_400, "Attempt to perform SPARQL update by GET. Use POST") ; + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doCommon(request, response) ; + } + + @Override + protected void doOptions(HttpServletRequest request, HttpServletResponse response) + { + response.setHeader(HttpNames.hAllow, "OPTIONS,POST"); + response.setHeader(HttpNames.hContentLengh, "0") ; + } + + @Override + protected void perform(HttpAction action) + { + // WebContent needs to migrate to using ContentType. + String ctStr ; + { + ContentType ct = FusekiLib.getContentType(action) ; + if ( ct == null ) + ctStr = WebContent.contentTypeSPARQLUpdate ; + else + ctStr = ct.getContentType() ; + } + + if (WebContent.contentTypeSPARQLUpdate.equals(ctStr)) + { + executeBody(action) ; + return ; + } + if (WebContent.contentTypeHTMLForm.equals(ctStr)) + { + executeForm(action) ; + return ; + } + error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Bad content type: " + action.request.getContentType()) ; + } + + protected static List<String> paramsForm = Arrays.asList(paramRequest, paramUpdate, + paramUsingGraphURI, paramUsingNamedGraphURI) ; + protected static List<String> paramsPOST = Arrays.asList(paramUsingGraphURI, paramUsingNamedGraphURI) ; + + @Override + protected void validate(HttpAction action) + { + HttpServletRequest request = action.request ; + + if ( ! HttpNames.METHOD_POST.equalsIgnoreCase(request.getMethod()) ) + errorMethodNotAllowed("SPARQL Update : use POST") ; + + ContentType incoming = FusekiLib.getContentType(action) ; + String ctStr = ( incoming == null ) ? WebContent.contentTypeSPARQLUpdate : incoming.getContentType() ; + // ---- + + if ( WebContent.contentTypeSPARQLUpdate.equals(ctStr) ) + { + String charset = request.getCharacterEncoding() ; + if ( charset != null && ! charset.equalsIgnoreCase(WebContent.charsetUTF8) ) + errorBadRequest("Bad charset: "+charset) ; + validate(request, paramsPOST) ; + return ; + } + + if ( WebContent.contentTypeHTMLForm.equals(ctStr) ) + { + int x = countParamOccurences(request, paramUpdate) + countParamOccurences(request, paramRequest) ; + if ( x == 0 ) + errorBadRequest("SPARQL Update: No 'update=' parameter") ; + if ( x != 1 ) + errorBadRequest("SPARQL Update: Multiple 'update=' parameters") ; + + String requestStr = request.getParameter(paramUpdate) ; + if ( requestStr == null ) + requestStr = request.getParameter(paramRequest) ; + if ( requestStr == null ) + errorBadRequest("SPARQL Update: No update= in HTML form") ; + validate(request, paramsForm) ; + return ; + } + + error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Must be "+WebContent.contentTypeSPARQLUpdate+" or "+WebContent.contentTypeHTMLForm+" (got "+ctStr+")") ; + } + + protected void validate(HttpServletRequest request, Collection<String> params) + { + if ( params != null ) + { + Enumeration<String> en = request.getParameterNames() ; + for ( ; en.hasMoreElements() ; ) + { + String name = en.nextElement() ; + if ( ! params.contains(name) ) + warning("SPARQL Update: Unrecognize request parameter (ignored): "+name) ; + } + } + } + + private void executeBody(HttpAction action) + { + InputStream input = null ; + try { input = action.request.getInputStream() ; } + catch (IOException ex) { errorOccurred(ex) ; } + + if ( action.verbose ) + { + // Verbose mode only .... capture request for logging (does not scale). + String requestStr = null ; + try { requestStr = IO.readWholeFileAsUTF8(input) ; } + catch (IOException ex) { IO.exception(ex) ; } + requestLog.info(format("[%d] Update = %s", action.id, formatForLog(requestStr))) ; + + input = new ByteArrayInputStream(requestStr.getBytes()); + requestStr = null; + } + + execute(action, input) ; + successNoContent(action) ; + } + + private void executeForm(HttpAction action) + { + String requestStr = action.request.getParameter(paramUpdate) ; + if ( requestStr == null ) + requestStr = action.request.getParameter(paramRequest) ; + + if ( action.verbose ) + //requestLog.info(format("[%d] Form update = %s", action.id, formatForLog(requestStr))) ; + requestLog.info(format("[%d] Form update = \n%s", action.id, requestStr)) ; + // A little ugly because we are taking a copy of the string, but hopefully shouldn't be too big if we are in this code-path + // If we didn't want this additional copy, we could make the parser take a Reader in addition to an InputStream + byte[] b = StrUtils.asUTF8bytes(requestStr) ; + ByteArrayInputStream input = new ByteArrayInputStream(b); + requestStr = null; // free it early at least + execute(action, input); + successPage(action,"Update succeeded") ; + } + + private void execute(HttpAction action, InputStream input) + { + UsingList usingList = processProtocol(action.request) ; + + // If the dsg is transactional, then we can parse and execute the update in a streaming fashion. + // If it isn't, we need to read the entire update request before performing any updates, because + // we have to attempt to make the request atomic in the face of malformed queries + UpdateRequest req = null ; + if (!action.isTransactional()) + { + try { + // TODO implement a spill-to-disk version of this + req = UpdateFactory.read(usingList, input, UpdateParseBase, Syntax.syntaxARQ); + } + catch (UpdateException ex) { errorBadRequest(ex.getMessage()) ; return ; } + catch (QueryParseException ex) { errorBadRequest(messageForQPE(ex)) ; return ; } + } + + action.beginWrite() ; + try { + if (req == null ) + UpdateAction.parseExecute(usingList, action.getActiveDSG(), input, UpdateParseBase, Syntax.syntaxARQ); + else + UpdateAction.execute(req, action.getActiveDSG()) ; + action.commit() ; + } catch (UpdateException ex) { + action.abort() ; + incCounter(action.srvRef, UpdateExecErrors) ; + errorOccurred(ex.getMessage()) ; + } catch (QueryParseException ex) { + action.abort() ; + // Counter inc'ed further out. + errorBadRequest(messageForQPE(ex)) ; + } catch (Throwable ex) { + if ( ! ( ex instanceof ActionErrorException ) ) + { + try { action.abort() ; } catch (Exception ex2) {} + errorOccurred(ex.getMessage(), ex) ; + } + } finally { action.endWrite(); } + } + + /* [It is an error to supply the using-graph-uri or using-named-graph-uri parameters + * when using this protocol to convey a SPARQL 1.1 Update request that contains an + * operation that uses the USING, USING NAMED, or WITH clause.] + * + * We will simply capture any using parameters here and pass them to the parser, which will be + * responsible for throwing an UpdateException if the query violates the above requirement, + * and will also be responsible for adding the using parameters to update queries that can + * accept them. + */ + private UsingList processProtocol(HttpServletRequest request) + { + UsingList toReturn = new UsingList(); + + String[] usingArgs = request.getParameterValues(paramUsingGraphURI) ; + String[] usingNamedArgs = request.getParameterValues(paramUsingNamedGraphURI) ; + if ( usingArgs == null && usingNamedArgs == null ) + return toReturn; + if ( usingArgs == null ) + usingArgs = new String[0] ; + if ( usingNamedArgs == null ) + usingNamedArgs = new String[0] ; + // Impossible. +// if ( usingArgs.length == 0 && usingNamedArgs.length == 0 ) +// return ; + + for (String nodeUri : usingArgs) + { + toReturn.addUsing(createNode(nodeUri)); + } + for (String nodeUri : usingNamedArgs) + { + toReturn.addUsingNamed(createNode(nodeUri)); + } + + return toReturn; + } + + private static Node createNode(String x) + { + try { + IRI iri = resolver.resolve(x) ; + return NodeFactory.createURI(iri.toString()) ; + } catch (Exception ex) + { + errorBadRequest("SPARQL Update: bad IRI: "+x) ; + return null ; + } + + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java new file mode 100644 index 0000000..d5956e5 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java @@ -0,0 +1,259 @@ +/* + * 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.servlets; + +import static java.lang.String.format ; + +import java.io.IOException ; +import java.io.InputStream ; +import java.io.PrintWriter ; +import java.util.zip.GZIPInputStream ; + +import javax.servlet.ServletException ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.commons.fileupload.FileItemIterator ; +import org.apache.commons.fileupload.FileItemStream ; +import org.apache.commons.fileupload.servlet.ServletFileUpload ; +import org.apache.commons.fileupload.util.Streams ; +import org.apache.jena.atlas.lib.Pair ; +import org.apache.jena.atlas.web.ContentType ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.iri.IRI ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFLanguages ; +import org.apache.jena.riot.lang.StreamRDFCounting ; +import org.apache.jena.riot.system.* ; +import org.apache.jena.web.HttpSC ; + +import com.hp.hpl.jena.graph.Graph ; +import com.hp.hpl.jena.graph.Node ; +import com.hp.hpl.jena.graph.NodeFactory ; +import com.hp.hpl.jena.sparql.core.Quad ; +import com.hp.hpl.jena.sparql.graph.GraphFactory ; + +public class SPARQL_Upload extends SPARQL_ServletBase +{ + private static ErrorHandler errorHandler = ErrorHandlerFactory.errorHandlerStd(log) ; + + public SPARQL_Upload() { + super() ; + } + + // Methods to respond to. + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doCommon(request, response) ; + } + + @Override + protected void doOptions(HttpServletRequest request, HttpServletResponse response) + { + response.setHeader(HttpNames.hAllow, "OPTIONS,POST"); + response.setHeader(HttpNames.hContentLengh, "0") ; + } + + @Override + protected void perform(HttpAction action) + { + // Only allows one file in the upload. + boolean isMultipart = ServletFileUpload.isMultipartContent(action.request); + if ( ! isMultipart ) + error(HttpSC.BAD_REQUEST_400 , "Not a file upload") ; + long count = upload(action, "http://example/upload-base/") ; + try { + action.response.setContentType("text/html") ; + action.response.setStatus(HttpSC.OK_200); + PrintWriter out = action.response.getWriter() ; + out.println("<html>") ; + out.println("<head>") ; + out.println("</head>") ; + out.println("<body>") ; + out.println("<h1>Success</h1>"); + out.println("<p>") ; + out.println("Triples = "+count + "\n"); + out.println("<p>") ; + out.println("</p>") ; + out.println("<button onclick=\"timeFunction()\">Back to Fuseki</button>"); + out.println("</p>") ; + out.println("<script type=\"text/javascript\">"); + out.println("function timeFunction(){"); + out.println("window.location.href = \"/fuseki.html\";}"); + out.println("</script>"); + out.println("</body>") ; + out.println("</html>") ; + out.flush() ; + success(action) ; + } + catch (Exception ex) { errorOccurred(ex) ; } + } + + // Also used by SPARQL_REST + static public long upload(HttpAction action, String base) + { + if ( action.isTransactional() ) + return uploadTxn(action, base) ; + else + return uploadNonTxn(action, base) ; + } + + /** Non-transaction - buffer to a temporary graph so that parse errors + * are caught before inserting any data. + */ + private static long uploadNonTxn(HttpAction action, String base) { + Pair<String, Graph> p = uploadWorker(action, base) ; + String graphName = p.getLeft() ; + Graph graphTmp = p.getRight() ; + long tripleCount = graphTmp.size() ; + + log.info(format("[%d] Upload: Graph: %s (%d triple(s))", + action.id, graphName, tripleCount)) ; + + Node gn = graphName.equals(HttpNames.valueDefault) + ? Quad.defaultGraphNodeGenerated + : NodeFactory.createURI(graphName) ; + + action.beginWrite() ; + try { + FusekiLib.addDataInto(graphTmp, action.getActiveDSG(), gn) ; + action.commit() ; + return tripleCount ; + } catch (RuntimeException ex) + { + // If anything went wrong, try to backout. + try { action.abort() ; } catch (Exception ex2) {} + errorOccurred(ex.getMessage()) ; + return -1 ; + } + finally { action.endWrite() ; } + } + + /** Transactional - we'd like data to go straight to the destination, with an abort on parse error. + * But file upload with a name means that the name can be after the data + * (it is in the Fuseki default pages). + * Use Graph Store protocol for bulk uploads. + * (It would be possible to process the incoming stream and see the graph name first.) + */ + private static long uploadTxn(HttpAction action, String base) { + // We can't do better than the non-transaction approach. + return uploadNonTxn(action, base) ; + } + + /** process an HTTP upload of RDF. + * We can't stream straight into a dataset because the graph name can be after the data. + * @return graph name and count + */ + + static private Pair<String, Graph> uploadWorker(HttpAction action, String base) + { + Graph graphTmp = GraphFactory.createDefaultGraph() ; + ServletFileUpload upload = new ServletFileUpload(); + String graphName = null ; + long count = -1 ; + + String name = null ; + ContentType ct = null ; + Lang lang = null ; + + try { + FileItemIterator iter = upload.getItemIterator(action.request); + while (iter.hasNext()) { + FileItemStream item = iter.next(); + String fieldName = item.getFieldName(); + InputStream stream = item.openStream(); + if (item.isFormField()) + { + // Graph name. + String value = Streams.asString(stream, "UTF-8") ; + if ( fieldName.equals(HttpNames.paramGraph) ) + { + graphName = value ; + if ( graphName != null && ! graphName.equals("") && ! graphName.equals(HttpNames.valueDefault) ) + { + IRI iri = IRIResolver.parseIRI(value) ; + if ( iri.hasViolation(false) ) + errorBadRequest("Bad IRI: "+graphName) ; + if ( iri.getScheme() == null ) + errorBadRequest("Bad IRI: no IRI scheme name: "+graphName) ; + if ( iri.getScheme().equalsIgnoreCase("http") || iri.getScheme().equalsIgnoreCase("https")) + { + // Redundant?? + if ( iri.getRawHost() == null ) + errorBadRequest("Bad IRI: no host name: "+graphName) ; + if ( iri.getRawPath() == null || iri.getRawPath().length() == 0 ) + errorBadRequest("Bad IRI: no path: "+graphName) ; + if ( iri.getRawPath().charAt(0) != '/' ) + errorBadRequest("Bad IRI: Path does not start '/': "+graphName) ; + } + } + } + else if ( fieldName.equals(HttpNames.paramDefaultGraphURI) ) + graphName = null ; + else + // Add file type? + log.info(format("[%d] Upload: Field=%s ignored", action.id, fieldName)) ; + } else { + // Process the input stream + name = item.getName() ; + if ( name == null || name.equals("") || name.equals("UNSET FILE NAME") ) + errorBadRequest("No name for content - can't determine RDF syntax") ; + + String contentTypeHeader = item.getContentType() ; + ct = ContentType.create(contentTypeHeader) ; + + lang = RDFLanguages.contentTypeToLang(ct.getContentType()) ; + + if ( lang == null ) { + lang = RDFLanguages.filenameToLang(name) ; + + //JENA-600 filenameToLang() strips off certain extensions such as .gz and + //we need to ensure that if there was a .gz extension present we wrap the stream accordingly + if (name.endsWith(".gz")) + stream = new GZIPInputStream(stream); + } + if ( lang == null ) + // Desperate. + lang = RDFLanguages.RDFXML ; + + log.info(format("[%d] Upload: Filename: %s, Content-Type=%s, Charset=%s => %s", + action.id, name, ct.getContentType(), ct.getCharset(), lang.getName())) ; + + StreamRDF x = StreamRDFLib.graph(graphTmp) ; + StreamRDFCounting dest = StreamRDFLib.count(x) ; + SPARQL_REST.parse(action, dest, stream, lang, base); + count = dest.count() ; + } + } + + if ( graphName == null || graphName.equals("") ) + graphName = HttpNames.valueDefault ; + return Pair.create(graphName, graphTmp) ; + } + catch (ActionErrorException ex) { throw ex ; } + catch (Exception ex) { errorOccurred(ex) ; return null ; } + } + + @Override + protected void validate(HttpAction action) + {} +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java new file mode 100644 index 0000000..473a7db --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java @@ -0,0 +1,237 @@ +/* + * 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.servlets; + +import java.io.IOException ; +import java.io.PrintWriter ; +import java.util.concurrent.atomic.AtomicLong ; + +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.lib.StrUtils ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.web.HttpSC ; +import org.slf4j.Logger ; + +/** + * An abstract HTTP Servlet. Contains implementation methods for setting the request status in a HTTP Action, + * and a mechanism to allocate unique ID's to new requests. + */ +public abstract class ServletBase extends HttpServlet +{ + protected static final Logger log = Fuseki.requestLog ; + public final boolean verboseLogging = Fuseki.verboseLogging ; + private static AtomicLong requestIdAlloc = new AtomicLong(0) ; + + protected ServletBase() { } + + /** + * Helper method which gets a unique request ID and appends it as a header to the response + * @param request HTTP Request + * @param response HTTP Response + * @return Request ID + */ + protected long allocRequestId(HttpServletRequest request, HttpServletResponse response) { + long id = requestIdAlloc.incrementAndGet(); + addRequestId(response, id); + return id; + } + + /** + * Helper method for attaching a request ID to a response as a header + * @param response Response + * @param id Request ID + */ + protected void addRequestId(HttpServletResponse response, long id) { + response.addHeader("Fuseki-Request-ID", Long.toString(id)); + } + + protected void responseSendError(HttpServletResponse response, int statusCode, String message) + { + try { response.sendError(statusCode, message) ; } + catch (IOException ex) { errorOccurred(ex) ; } + catch (IllegalStateException ex) { } + } + + protected void responseSendError(HttpServletResponse response, int statusCode) + { + try { response.sendError(statusCode) ; } + catch (IOException ex) { errorOccurred(ex) ; } + } + + /** + * Returns the HTTP request URL, appended with any additional URL parameters used. + * + * @param request HTTP request + * @return complete request URL + */ + protected static String wholeRequestURL(HttpServletRequest request) + { + StringBuffer sb = request.getRequestURL() ; + String queryString = request.getQueryString() ; + if ( queryString != null ) + { + sb.append("?") ; + sb.append(queryString) ; + } + return sb.toString() ; + } + + protected static void successNoContent(HttpAction action) + { + success(action, HttpSC.NO_CONTENT_204); + } + + protected static void success(HttpAction action) + { + success(action, HttpSC.OK_200); + } + + protected static void successCreated(HttpAction action) + { + success(action, HttpSC.CREATED_201); + } + + // When 404 is no big deal e.g. HEAD + protected static void successNotFound(HttpAction action) + { + success(action, HttpSC.NOT_FOUND_404) ; + } + + // + protected static void success(HttpAction action, int httpStatusCode) + { + action.response.setStatus(httpStatusCode); + } + + protected static void successPage(HttpAction action, String message) + { + try { + action.response.setContentType("text/html"); + action.response.setStatus(HttpSC.OK_200); + PrintWriter out = action.response.getWriter() ; + out.println("<html>") ; + out.println("<head>") ; + out.println("</head>") ; + out.println("<body>") ; + out.println("<h1>Success</h1>"); + if ( message != null ) + { + out.println("<p>") ; + out.println(message) ; + out.println("</p>") ; + } + out.println("</body>") ; + out.println("</html>") ; + out.flush() ; + } catch (IOException ex) { errorOccurred(ex) ; } + } + + protected static void warning(String string) + { + log.warn(string) ; + } + + protected static void warning(String string, Throwable thorwable) + { + log.warn(string, thorwable) ; + } + + protected static void errorBadRequest(String string) + { + error(HttpSC.BAD_REQUEST_400, string) ; + } + + protected static void errorNotFound(String string) + { + error(HttpSC.NOT_FOUND_404, string) ; + } + + protected static void errorNotImplemented(String msg) + { + error(HttpSC.NOT_IMPLEMENTED_501, msg) ; + } + + protected static void errorMethodNotAllowed(String method) + { + error(HttpSC.METHOD_NOT_ALLOWED_405, "HTTP method not allowed: "+method) ; + } + + protected static void errorForbidden(String msg) + { + if ( msg != null ) + error(HttpSC.FORBIDDEN_403, msg) ; + else + error(HttpSC.FORBIDDEN_403, "Forbidden") ; + } + + protected static void error(int statusCode) + { + throw new ActionErrorException(null, null, statusCode) ; + } + + + protected static void error(int statusCode, String string) + { + throw new ActionErrorException(null, string, statusCode) ; + } + + protected static void errorOccurred(String message) + { + errorOccurred(message, null) ; + } + + protected static void errorOccurred(Throwable ex) + { + errorOccurred(null, ex) ; + } + + protected static void errorOccurred(String message, Throwable ex) + { + throw new ActionErrorException(ex, message, HttpSC.INTERNAL_SERVER_ERROR_500) ; + } + + protected static String formatForLog(String string) + { + string = string.replace('\n', ' ') ; + string = string.replace('\r', ' ') ; + return string ; + } + + static String varyHeaderSetting = + StrUtils.strjoin(",", + HttpNames.hAccept, + HttpNames.hAcceptEncoding, + HttpNames.hAcceptCharset ) ; + + public static void setVaryHeader(HttpServletResponse httpResponse) + { + httpResponse.setHeader(HttpNames.hVary, varyHeaderSetting) ; + } + + + public static void setCommonHeaders(HttpServletResponse httpResponse) + { + httpResponse.setHeader(HttpNames.hAccessControlAllowOrigin, "*") ; + httpResponse.setHeader(HttpNames.hServer, Fuseki.serverHttpName) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java new file mode 100644 index 0000000..7f5cad9 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java @@ -0,0 +1,78 @@ +/** + * 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.servlets; + +import java.io.IOException ; +import java.io.Writer ; +import java.util.Map ; + +import org.apache.velocity.Template ; +import org.apache.velocity.VelocityContext ; +import org.apache.velocity.app.VelocityEngine ; +import org.apache.velocity.exception.MethodInvocationException ; +import org.apache.velocity.exception.ParseErrorException ; +import org.apache.velocity.exception.ResourceNotFoundException ; +import org.apache.velocity.runtime.RuntimeConstants ; +import org.apache.velocity.runtime.log.LogChute ; +import org.apache.velocity.runtime.log.NullLogChute ; +import org.slf4j.Logger ; +import org.slf4j.LoggerFactory ; + +public class SimpleVelocity +{ + private static LogChute velocityLogChute = new NullLogChute() ; + private static Logger velocityLog = LoggerFactory.getLogger("Velocity"); + + /** Process a template */ + public static void process(String base, String path, Writer out, Map<String, Object> params) + { + process(base, path, out, createContext(params)) ; + } + + /** Process a template */ + public static void process(String base, String path, Writer out, VelocityContext context) + { + VelocityEngine velocity = new VelocityEngine() ; + // Turn off logging - catch exceptions and log ourselves + velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, velocityLogChute) ; + velocity.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8") ; + velocity.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, base) ; + velocity.init() ; + try { + Template temp = velocity.getTemplate(path) ; + temp.merge(context, out) ; + out.flush(); + } + catch (ResourceNotFoundException ex) { velocityLog.error("Resource not found: "+ex.getMessage()) ; } + catch (ParseErrorException ex) { velocityLog.error("Parse error ("+path+") : "+ex.getMessage()) ; } + catch (MethodInvocationException ex) { velocityLog.error("Method invocation exception ("+path+") : "+ex.getMessage()) ; } + catch (IOException ex) { velocityLog.warn("IOException", ex) ; } + } + + public static VelocityContext createContext(Map<String, Object> params) + { + // Velocity requires a mutable map. + // Scala leads to immutable maps ... be safe and copy. + VelocityContext context = new VelocityContext() ; + for ( Map.Entry<String, Object> e : params.entrySet() ) + context.put(e.getKey(), e.getValue()) ; + return context ; + } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java new file mode 100644 index 0000000..8773eba --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java @@ -0,0 +1,178 @@ +/** + * 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.servlets; + +import java.io.IOException ; +import java.io.Writer ; +import java.util.Map ; + +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.velocity.VelocityContext ; +import org.apache.velocity.app.VelocityEngine ; +import org.apache.velocity.runtime.RuntimeConstants ; +import org.apache.velocity.runtime.RuntimeServices ; +import org.apache.velocity.runtime.log.LogChute ; +import org.apache.velocity.runtime.log.NullLogChute ; +import org.slf4j.Logger ; +import org.slf4j.LoggerFactory ; + + +/** Simple servlet that uses <a href="http://velocity.apache.org/">Velocity</a> + * to format pages. It isolates the use of velocity by taking a configuration map. + * Use with a servlet mapping of "*.vm" or some such extension. + */ +public class SimpleVelocityServlet extends HttpServlet +{ + //private static Logger log = LoggerFactory.getLogger(SimpleVelocityServlet.class) ; + /* Velocity logging + * Instead of internal velocity logging, we catch the exceptions, + * log the message ourselves. This gives a celaner log file without + * loosing information that the application could use. + */ + + private static Logger vlog = LoggerFactory.getLogger("Velocity") ; + private static LogChute velocityLog = new NullLogChute() ; + //private static LogChute velocityLog = new SimpleSLF4JLogChute(vlog) ; + + private String docbase ; + private VelocityEngine velocity ; + private String functionsName = null ; + private final Map<String, Object> datamodel ; + + public SimpleVelocityServlet(String base, Map<String, Object> datamodel) + { + this.docbase = base ; + this.datamodel = datamodel ; + velocity = new VelocityEngine(); + // Turn off logging - catch exceptions and log ourselves + velocity.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, velocityLog) ; + velocity.setProperty( RuntimeConstants.INPUT_ENCODING, "UTF-8" ) ; + velocity.setProperty( RuntimeConstants.FILE_RESOURCE_LOADER_PATH, base) ; + velocity.init(); + } + + // See also + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + { + process(req, resp) ; + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + { + process(req, resp) ; + } + + private void process(HttpServletRequest req, HttpServletResponse resp) + { + try + { + resp.setContentType("text/html") ; + resp.setCharacterEncoding("UTF-8") ; + Writer out = resp.getWriter() ; + String path = path(req) ; + VelocityContext vc = SimpleVelocity.createContext(datamodel) ; + vc.put("request", req) ; + SimpleVelocity.process(docbase, path, out, vc) ; + } catch (IOException ex) + { + vlog.warn("IOException", ex) ; + } + } + + private String path(HttpServletRequest request) + { + String path = request.getPathInfo(); + if (path != null) return path; + path = request.getServletPath(); + if (path != null) return path; + return null ; + } + + @Override + public String getServletInfo() + { + return "Lightweight Velocity Servlet"; + } + + /** Velocity logger to SLF4J */ + static class SimpleSLF4JLogChute implements LogChute + { + // Uusally for debugging only. + private Logger logger ; + + SimpleSLF4JLogChute( Logger log ) + { + this.logger = log ; + } + + @Override + public void init(RuntimeServices rs) throws Exception + { } + + @Override + public void log(int level, String message) + { + if ( logger == null ) return ; + switch(level) + { + case LogChute.TRACE_ID : logger.trace(message) ; return ; + case LogChute.DEBUG_ID : logger.debug(message) ; return ; + case LogChute.INFO_ID : logger.info(message) ; return ; + case LogChute.WARN_ID : logger.warn(message) ; return ; + case LogChute.ERROR_ID : logger.error(message) ; return ; + } + } + + @Override + public void log(int level, String message, Throwable t) + { + if ( logger == null ) return ; + // Forget the stack trace - velcoity internal - long - unhelpful to application. + t = null ; + switch (level) + { + case LogChute.TRACE_ID : logger.trace(message, t) ; return ; + case LogChute.DEBUG_ID : logger.debug(message, t) ; return ; + case LogChute.INFO_ID : logger.info(message, t) ; return ; + case LogChute.WARN_ID : logger.warn(message, t) ; return ; + case LogChute.ERROR_ID : logger.error(message, t) ; return ; + } + } + + @Override + public boolean isLevelEnabled(int level) + { + switch(level) + { + case LogChute.TRACE_ID: return logger.isTraceEnabled() ; + case LogChute.DEBUG_ID: return logger.isDebugEnabled() ; + case LogChute.INFO_ID: return logger.isInfoEnabled() ; + case LogChute.WARN_ID: return logger.isWarnEnabled() ; + case LogChute.ERROR_ID: return logger.isErrorEnabled() ; + } + return true ; + } + } +} +
