http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java new file mode 100644 index 0000000..11c7330 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java @@ -0,0 +1,32 @@ +/** + * 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.server; + +public interface ServiceMXBean +{ + String getName() ; + + long getRequests() ; + long getRequestsGood() ; + long getRequestsBad() ; + +// void enable() ; +// void disable() ; +} +
http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java new file mode 100644 index 0000000..6236050 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java @@ -0,0 +1,63 @@ +/** + * 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.server; + +import java.util.ArrayList ; +import java.util.List ; + +/** Configuration of an individual service */ +public class ServiceRef implements ServiceMXBean, Counters +{ + public final String name ; + + // Service-level counters. + private final CounterSet counters = new CounterSet() ; + @Override + public CounterSet getCounters() { return counters ; } + + /** Endpoints (as absolute path URLs) */ + public List<String> endpoints = new ArrayList<String>() ; + + // Attach counters to services or datasets + // Can we have a counter of the same name on different services? + // Cost : number of maps. + // +ve: Multiple services with the same name counter + + public ServiceRef(String serviceName) { + this.name = serviceName ; + } + + public boolean isActive() { return endpoints.isEmpty() ; } + + @Override + public String getName() { return name ; } + + @Override public long getRequests() { + return counters.value(CounterName.Requests) ; + } + @Override + public long getRequestsGood() { + return counters.value(CounterName.RequestsGood) ; + } + @Override + public long getRequestsBad() { + return counters.value(CounterName.RequestsBad) ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java new file mode 100644 index 0000000..6c5ebe9 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java @@ -0,0 +1,32 @@ +/* + * 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; + +class ActionErrorException extends RuntimeException +{ + final Throwable exception ; + final String message ; + final int rc ; + ActionErrorException(Throwable ex, String message, int rc) + { + this.exception = ex ; + this.message = message ; + this.rc = rc ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java new file mode 100644 index 0000000..259453d --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java @@ -0,0 +1,113 @@ +/* + * 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.util.ConcurrentModificationException ; +import java.util.concurrent.atomic.AtomicLong ; + +import org.apache.jena.fuseki.Fuseki ; +import org.slf4j.Logger ; + +public final class ConcurrencyPolicyMRSW +{ + static private Logger log = Fuseki.requestLog ; //org.slf4j.LoggerFactory.getLogger(ConcurrencyPolicyMRSW.class) ; + static private final boolean logging = false ; //log.isDebugEnabled() ; + + // This is a simplified version of ConcurrencyPolicyMRSW from TDB. + private final AtomicLong readCounter = new AtomicLong(0) ; + private final AtomicLong writeCounter = new AtomicLong(0) ; + static private final AtomicLong policyCounter = new AtomicLong(0) ; + + public ConcurrencyPolicyMRSW() + { policyCounter.incrementAndGet() ; } + + // Loggin -inside the operation. + + //@Override + public void startRead() + { + readCounter.getAndIncrement() ; + log() ; + checkConcurrency() ; + } + + //@Override + public void finishRead() + { + log() ; + readCounter.decrementAndGet() ; + checkConcurrency() ; + } + + //@Override + public void startUpdate() + { + writeCounter.getAndIncrement() ; + log() ; + checkConcurrency() ; + } + + //@Override + public void finishUpdate() + { + log() ; + writeCounter.decrementAndGet() ; + checkConcurrency() ; + } + + private synchronized void checkConcurrency() + { + long R = readCounter.get() ; + long W = writeCounter.get() ; + long id = policyCounter.get(); + if ( R > 0 && W > 0 ) + policyError(id, R, W) ; + if ( W > 1 ) + policyError(id, R, W) ; + } + + private void log() + { + if ( ! logging ) + return ; + long R , W , id ; + synchronized(this) + { + R = readCounter.get() ; + W = writeCounter.get() ; + id = policyCounter.get(); + } + log.info(format(id, R, W)) ; + } + + private static void policyError(long id, long R, long W) + { + policyError(format(id, R, W)) ; + } + + private static void policyError(String message) + { + throw new ConcurrentModificationException(message) ; + } + + private static String format(long id, long R, long W) + { + return String.format("(lock=%d) Reader = %d, Writer = %d", id, R, W) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java new file mode 100644 index 0000000..7ece249 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java @@ -0,0 +1,313 @@ +/* + * 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. + */ + +/** A servlet that dumps its request + */ + +// Could be neater - much, much neater! +package org.apache.jena.fuseki.servlets; + +import java.io.BufferedReader ; +import java.io.IOException ; +import java.io.PrintWriter ; +import java.io.StringWriter ; +import java.util.Date ; +import java.util.Enumeration ; +import java.util.Locale ; +import java.util.Properties ; + +import javax.servlet.ServletContext ; +import javax.servlet.http.Cookie ; +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +public class DumpServlet extends HttpServlet +{ + private static final long serialVersionUID = 99L; // Serilizable. + + + public DumpServlet() + { + + } + + @Override + public void init() + { + return ; + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + { + try { + PrintWriter out = resp.getWriter() ; + resp.setContentType("text/html"); + + String now = new Date().toString() ; + + // HEAD + out.println("<html>") ; + out.println("<head>") ; + out.println("<Title>Dump @ "+now+"</Title>") ; + // Reduce the desire to cache it. + out.println("<meta CONTENT=now HTTP-EQUIV=expires>") ; + out.println("</head>") ; + + // BODY + out.println("<body>") ; + out.println("<pre>") ; + + out.println("Dump : "+now); + out.println() ; + out.println("==== Request"); + out.println() ; + out.print(dumpRequest(req)) ; + out.println() ; + + out.println(">>>> Body"); + out.println() ; + printBody(out, req) ; + out.println("<<<< Body"); + + out.println("==== ServletContext"); + out.println() ; + out.print(dumpServletContext()); + out.println() ; + + out.println("==== Environment"); + out.println() ; + out.print(dumpEnvironment()); + out.println() ; + + out.println("</pre>") ; + + out.println("</body>") ; + out.println("</html>") ; + out.flush() ; + } catch (IOException e) + { } + } + + // This resets the input stream + + static public String dumpRequest(HttpServletRequest req) + { + StringWriter sw = new StringWriter() ; + try( PrintWriter pw = new PrintWriter(sw) ) { + // Standard environment + pw.println("Method: "+req.getMethod()); + pw.println("getContentLength: "+Integer.toString(req.getContentLength())); + pw.println("getContentType: "+req.getContentType()); + pw.println("getRequestURI: "+req.getRequestURI()); + pw.println("getRequestURL: "+req.getRequestURL()); + pw.println("getContextPath: "+req.getContextPath()); + pw.println("getServletPath: "+req.getServletPath()); + pw.println("getPathInfo: "+req.getPathInfo()); + pw.println("getPathTranslated: "+req.getPathTranslated()); + pw.println("getQueryString: "+req.getQueryString()); + pw.println("getProtocol: "+req.getProtocol()); + pw.println("getScheme: "+req.getScheme()); + pw.println("getServerName: "+req.getServerName()); + pw.println("getServerPort: "+req.getServerPort()); + pw.println("getRemoteUser: "+req.getRemoteUser()); + pw.println("getRemoteAddr: "+req.getRemoteAddr()); + pw.println("getRemoteHost: "+req.getRemoteHost()); + pw.println("getRequestedSessionId: "+req.getRequestedSessionId()); + { + Cookie c[] = req.getCookies() ; + if ( c == null ) + pw.println("getCookies: <none>"); + else + { + for ( Cookie aC : c ) + { + pw.println( "Cookie: " + aC.getName() ); + pw.println( " value: " + aC.getValue() ); + pw.println( " version: " + aC.getVersion() ); + pw.println( " comment: " + aC.getComment() ); + pw.println( " domain: " + aC.getDomain() ); + pw.println( " maxAge: " + aC.getMaxAge() ); + pw.println( " path: " + aC.getPath() ); + pw.println( " secure: " + aC.getSecure() ); + pw.println(); + } + } + } + + { + // To do: create a string for the output so can send to console and return it. + Enumeration<String> en = req.getHeaderNames() ; + + for ( ; en.hasMoreElements() ; ) + { + String name = en.nextElement() ; + String value = req.getHeader(name) ; + pw.println("Head: "+name + " = " + value) ; + } + } + + Enumeration<String> en2 = req.getAttributeNames() ; + if ( en2.hasMoreElements() ) + pw.println(); + for ( ; en2.hasMoreElements() ; ) + { + String name = en2.nextElement() ; + String value = req.getAttribute(name).toString() ; + pw.println("Attr: "+name + " = " + value) ; + } + + // Note that doing this on a form causes the forms content (body) to be read + // and parsed as form variables. +// en = req.getParameterNames() ; +// if ( en.hasMoreElements() ) +// pw.println(); +// for ( ; en.hasMoreElements() ; ) +// { +// String name = (String)en.nextElement() ; +// String value = req.getParameter(name) ; +// pw.println("Param: "+name + " = " + value) ; +// } + + + +// MultiMap<String, String> map = WebLib.parseQueryString(req) ; +// for ( String name : map.keys() ) +// for ( String value : map.get(name) ) +// pw.println("Param: "+name + " = " + value) ; + + Enumeration<Locale> en = req.getLocales() ; + if ( en.hasMoreElements() ) + pw.println(); + for ( ; en.hasMoreElements() ; ) + { + String name = en.nextElement().toString() ; + pw.println("Locale: "+name) ; + } + + pw.println() ; + pw.flush(); + //printBody(pw, req) ; + return sw.toString() ; + } + + } + + static void printBody(PrintWriter pw, HttpServletRequest req) throws IOException + { + BufferedReader in = req.getReader() ; + if ( req.getContentLength() > 0 ) + // Need +2 because last line may not have a CR/LF on it. + in.mark(req.getContentLength()+2) ; + else + // This is a dump - try to do something that works, even if inefficient. + in.mark(100*1024) ; + + while(true) + { + String x = in.readLine() ; + if ( x == null ) + break ; + x = x.replaceAll("&", "&") ; + x = x.replaceAll("<", "<") ; + x = x.replaceAll(">", ">") ; + pw.println(x) ; + } + try { in.reset() ;} catch (IOException e) { System.out.println("DumpServlet: Reset of content failed: "+e) ; } + } + + /** + * <code>dumpEnvironment</code> + * @return String that is the HTML of the System properties as name/value pairs. + * The values are with single quotes independent of whether or not the value has + * single quotes in it. + */ + static public String dumpEnvironment() + { + Properties properties = System.getProperties(); + + StringWriter sw = new StringWriter() ; + try(PrintWriter pw = new PrintWriter(sw) ) { + Enumeration<Object> en = properties.keys(); + while(en.hasMoreElements()) + { + String key = en.nextElement().toString(); + pw.println(key+": '"+properties.getProperty(key)+"'"); + } + pw.println() ; + pw.flush() ; + return sw.toString() ; + } + } + + public String dumpServletContext() + { + StringWriter sw = new StringWriter() ; + try(PrintWriter pw = new PrintWriter(sw)) { + + ServletContext sc = getServletContext(); + pw.println("majorVersion: '"+sc.getMajorVersion()+"'"); + pw.println("minorVersion: '"+sc.getMinorVersion()+"'"); + pw.println("contextName: '"+sc.getServletContextName()+"'"); + pw.println("servletInfo: '"+getServletInfo()+"'"); + pw.println("serverInfo: '"+sc.getServerInfo()+"'"); + + { + Enumeration<String> en = sc.getInitParameterNames(); + if (en != null) { + pw.println("initParameters: "); + while(en.hasMoreElements()) + { + String key = en.nextElement(); + pw.println(key+": '"+sc.getInitParameter(key)+"'"); + } + } + } + + { + Enumeration<String> en = sc.getAttributeNames(); + if (en != null) { + pw.println("attributes: "); + while(en.hasMoreElements()) + { + String key = en.nextElement(); + pw.println(key+": '"+sc.getAttribute(key)+"'"); + } + } + } + pw.println() ; + pw.close() ; + } + return sw.toString() ; + } + + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + { + doGet(req, resp) ; + } + + + @Override + public String getServletInfo() + { + return "Dump"; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java new file mode 100644 index 0000000..39663dd --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java @@ -0,0 +1,290 @@ +/* + * 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 com.hp.hpl.jena.query.ReadWrite.READ ; +import static com.hp.hpl.jena.query.ReadWrite.WRITE ; + +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 ; +import org.apache.jena.atlas.web.MediaType ; +import org.apache.jena.fuseki.DEF ; +import org.apache.jena.fuseki.conneg.ConNeg ; +import org.apache.jena.fuseki.server.DatasetRef ; +import org.apache.jena.fuseki.server.ServiceRef ; + +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.sparql.SystemARQ ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DatasetGraphWithLock ; +import com.hp.hpl.jena.sparql.core.DatasetGraphWrapper ; +import com.hp.hpl.jena.sparql.core.Transactional ; + +/** + * HTTP action that represents the user request lifecycle. Its state is handled in the + * {@link SPARQL_ServletBase#executeLifecycle(HttpAction)} method. + */ +public class HttpAction +{ + public final long id ; + public final boolean verbose ; + + // Phase two items - set and valida after the datasetRef is known. + private DatasetGraph dsg ; // The data + public DatasetRef dsRef ; + public ServiceRef srvRef ; + + private Transactional transactional ; + private boolean isTransactional; + private DatasetGraph activeDSG ; // Set when inside begin/end. + private ReadWrite activeMode ; // Set when inside begin/end. + + private boolean startTimeIsSet = false ; + private boolean finishTimeIsSet = false ; + + private long startTime = -2 ; + private long finishTime = -2 ; + + // Incoming + //public final + + // Outcome. + int statusCode = -1 ; + String message = null ; + int contentLength = -1 ; + String contentType = null ; + + // Cleared to archive: + Map <String, String> headers = new HashMap<String, String>() ; + public HttpServletRequest request; + public HttpServletResponseTracker response ; + + /** + * Creates a new HTTP Action, using the HTTP request and response, and a given ID. + * + * @param id given ID + * @param request HTTP request + * @param response HTTP response + * @param verbose verbose flag + */ + public HttpAction(long id, HttpServletRequest request, HttpServletResponse response, boolean verbose) { + this.id = id ; + this.request = request ; + this.response = new HttpServletResponseTracker(this, response) ; + // Should this be set when setDataset is called from the dataset context? + // Currently server-wide, e.g. from the command line. + this.verbose = verbose ; + } + + /** + * <p>Sets the action dataset. Setting a {@link DatasetRef} will replace any existing DatasetRef, as well as + * as the {@link DatasetGraph} of the current HTTP Action.</p> + * + * <p>Once it has updated its members, the HTTP Action will change its transactional state and + * {@link Transactional} instance according to its base dataset graph.</p> + * + * @param desc {@link DatasetRef} + * @see Transactional + * @see DatasetGraphWrapper + */ + public void setDataset(DatasetRef desc) { + this.dsRef = desc ; + this.dsg = desc.dataset ; + DatasetGraph basedsg = unwrap(dsg) ; + + if ( isTransactional(basedsg) && isTransactional(dsg) ) { + // Use transactional if it looks safe - abort is necessary. + transactional = (Transactional)dsg ; + isTransactional = true ; + } else { + // Unsure if safe + transactional = new DatasetGraphWithLock(dsg) ; + // No real abort. + isTransactional = false ; + } + } + + /** + * Returns <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional}, + * <code>false otherwise</code>. + * + * @param dsg a {@link DatasetGraph} + * @return <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional}, + * <code>false otherwise</code> + */ + private static boolean isTransactional(DatasetGraph dsg) { + return (dsg instanceof Transactional) ; + } + + /** + * A {@link DatasetGraph} may contain other <strong>wrapped DatasetGraph's</strong>. This method will return + * the first instance (including the argument to this method) that <strong>is not</strong> an instance of + * {@link DatasetGraphWrapper}. + * + * @param dsg a {@link DatasetGraph} + * @return the first found {@link DatasetGraph} that is not an instance of {@link DatasetGraphWrapper} + */ + private static DatasetGraph unwrap(DatasetGraph dsg) { + while (dsg instanceof DatasetGraphWrapper) { + dsg = ((DatasetGraphWrapper)dsg).getWrapped() ; + } + return dsg ; + } + + /** + * Sets the {@link ServiceRef}. + * + * @param srvRef a {@link ServiceRef} + */ + public void setService(ServiceRef srvRef) { + this.srvRef = srvRef ; + } + + /** + * Returns whether or not the underlying DatasetGraph is fully transactional (supports rollback). + * @return <code>true</code> if the underlying DatasetGraph is fully transactional (supports rollback), + * <code>false</code> otherwise. + */ + public boolean isTransactional() { + return isTransactional ; + } + + public void beginRead() { + activeMode = READ ; + transactional.begin(READ) ; + activeDSG = dsg ; + dsRef.startTxn(READ) ; + } + + public void endRead() { + dsRef.finishTxn(READ) ; + activeMode = null ; + transactional.end() ; + activeDSG = null ; + } + + public void beginWrite() { + transactional.begin(WRITE) ; + activeMode = WRITE ; + activeDSG = dsg ; + dsRef.startTxn(WRITE) ; + } + + public void commit() { + transactional.commit() ; + activeDSG = null ; + } + + public void abort() { + try { transactional.abort() ; } + catch (Exception ex) { + // Some datasets claim to be transactional but + // don't provide a real abort. We tried to avoid + // them earlier but even if they sneek through, + // we try to continue server operation. + Log.warn(this, "Exception during abort (operation attempts to continue): "+ex.getMessage()) ; + } + activeDSG = null ; + } + + public void endWrite() { + dsRef.finishTxn(WRITE) ; + activeMode = null ; + + if ( transactional.isInTransaction() ) { + Log.warn(this, "Transaction still active in endWriter - no commit or abort seen (forced abort)") ; + try { + transactional.abort() ; + } catch (RuntimeException ex) { + Log.warn(this, "Exception in forced abort (trying to continue)", ex) ; + } + } + transactional.end() ; + activeDSG = null ; + } + + public final DatasetGraph getActiveDSG() { + return activeDSG ; + } + + public final DatasetRef getDatasetRef() { + return dsRef ; + } + + /** 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 HttpServletResponseTracker 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) ; + } + + public void sync() { + SystemARQ.sync(dsg) ; + } + + public static MediaType contentNegotationRDF(HttpAction action) { + MediaType mt = ConNeg.chooseContentType(action.request, DEF.rdfOffer, DEF.acceptRDFXML) ; + 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 ; + } + + public static MediaType contentNegotationQuads(HttpAction action) { + return ConNeg.chooseContentType(action.request, DEF.quadsOffer, DEF.acceptNQuads) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java new file mode 100644 index 0000000..c39e728 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java @@ -0,0 +1,140 @@ +/* + * 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 javax.servlet.http.HttpServletResponse ; +import javax.servlet.http.HttpServletResponseWrapper ; + +import org.apache.jena.atlas.logging.Log ; + +/** Intercepting wrapper so we can track the response settings for logging purposes */ + +public class HttpServletResponseTracker extends HttpServletResponseWrapper +{ + private final HttpAction action ; + + public HttpServletResponseTracker(HttpAction action, HttpServletResponse response) + { + super(response) ; + this.action = action ; + } + + @Override + public void sendError(int sc, String msg) throws IOException + { + action.statusCode = sc ; + action.message = msg ; + super.sendError(sc, msg) ; + } + + @Override + public void sendError(int sc) throws IOException + { + action.statusCode = sc ; + action.message = null ; + super.sendError(sc) ; + } + + @Override + public void setHeader(String name, String value) + { + super.setHeader(name, value) ; + action.headers.put(name, value) ; + } + + @Override + public void addHeader(String name, String value) + { + Log.warn(this, "Unexpected addHeader - not recorded in log") ; + super.addHeader(name, value) ; + } + @Override + public void setStatus(int sc) + { + action.statusCode = sc ; + action.message = null ; + super.setStatus(sc) ; + } + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + action.statusCode = sc ; + action.message = sm ; + super.setStatus(sc, sm) ; + } + + @Override + public void setContentLength(int len) + { + action.contentLength = len ; + super.setContentLength(len) ; + } + + @Override + public void setContentType(String type) + { + action.contentType = type ; + super.setContentType(type) ; + } + + // From HttpServletResponse +// public void addCookie(Cookie cookie) {} +// public boolean containsHeader(String name) {} +// public String encodeURL(String url) { } +// public String encodeRedirectURL(String url) {} +// public String encodeUrl(String url) {} +// public String encodeRedirectUrl(String url) {} +// public void sendError(int sc, String msg) throws IOException +// public void sendError(int sc) throws IOException +// public void sendRedirect(String location) throws IOException {} +// public void setDateHeader(String name, long date) {} +// public void addDateHeader(String name, long date) {} +// public void setHeader(String name, String value) +// public void addHeader(String name, String value) +// public void setIntHeader(String name, int value) {} +// public void addIntHeader(String name, int value) {} +// public void setStatus(int sc) +// public void setStatus(int sc, String sm) +// public void sendRedirect(String location) throws IOException {} +// public void setDateHeader(String name, long date) {} +// public void addDateHeader(String name, long date) {} + + // From ServletResponse. +// public ServletResponse getResponse() {} +// public void setResponse(ServletResponse response) {} +// public void setCharacterEncoding(String charset) {} +// public String getCharacterEncoding() {} +// public ServletOutputStream getOutputStream() throws IOException {} +// public PrintWriter getWriter() throws IOException {} +// public void setContentLength(int len) {} +// public void setContentType(String type) {} +// public String getContentType() { +// public void setBufferSize(int size) {} +// public int getBufferSize() {} +// public void flushBuffer() throws IOException {} +// public boolean isCommitted() {} +// public void reset() {} +// public void resetBuffer() {} +// public void setLocale(Locale loc) {} +// public Locale getLocale() {} +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java new file mode 100644 index 0000000..63e6562 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java @@ -0,0 +1,53 @@ +/* + * 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.* ; + +/** +* Code needed to implement an OutputStream that does nothing. +*/ + + +public class NullOutputStream extends /*Filter*/OutputStream +{ + public NullOutputStream() + { + } + + // The OutputStream operations + @Override + public void close() { /* .close() ;*/ } + @Override + public void flush() { /* .flush() ;*/ } + + // Need to implement this one. + @Override + public void write(int b) { /* .write(b) ;*/ } + @Override + public void write(byte b[]) { /* this.write(b, 0, b.length) ; */} + + // Good to implement this one. + @Override + public void write(byte[] b, int off, int len) + { + // Work function + } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java new file mode 100644 index 0000000..06c38b7 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java @@ -0,0 +1,211 @@ +/** + * 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 javax.servlet.ServletOutputStream ; + +import org.apache.jena.atlas.web.MediaType ; +import org.apache.jena.atlas.web.TypedOutputStream ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.riot.RDFLanguages ; +import org.apache.jena.riot.ReaderRIOT ; +import org.apache.jena.riot.system.StreamRDF ; +import org.apache.jena.riot.system.StreamRDFLib ; + +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.DatasetGraph ; + +/** + * Servlet that serves up quads for a dataset. + */ + +public class REST_Quads extends SPARQL_REST +{ + public REST_Quads() { super(); } + + @Override + protected void validate(HttpAction action) + { + // already checked? + } + + @Override + protected void doGet(HttpAction action) + { + MediaType mediaType = HttpAction.contentNegotationQuads(action) ; + ServletOutputStream output ; + try { output = action.response.getOutputStream() ; } + catch (IOException ex) { errorOccurred(ex) ; output = null ; } + + TypedOutputStream out = new TypedOutputStream(output, mediaType) ; + Lang lang = RDFLanguages.contentTypeToLang(mediaType.getContentType()) ; + if ( lang == null ) + lang = RDFLanguages.TRIG ; + + if ( action.verbose ) + log.info(format("[%d] Get: Content-Type=%s, Charset=%s => %s", + action.id, mediaType.getContentType(), mediaType.getCharset(), lang.getName())) ; + if ( ! RDFLanguages.isQuads(lang) ) + errorBadRequest("Not a quads format: "+mediaType) ; + + action.beginRead() ; + try { + DatasetGraph dsg = action.getActiveDSG() ; + RDFDataMgr.write(out, dsg, lang) ; + success(action) ; + } finally { action.endRead() ; } + } + + @Override + protected void doOptions(HttpAction action) + { + action.response.setHeader(HttpNames.hAllow, "GET, HEAD, OPTIONS") ; + action.response.setHeader(HttpNames.hContentLengh, "0") ; + success(action) ; + } + + @Override + protected void doHead(HttpAction action) + { + action.beginRead() ; + try { + MediaType mediaType = HttpAction.contentNegotationQuads(action) ; + success(action) ; + } finally { action.endRead() ; } + } + + static int counter = 0 ; + @Override + protected void doPost(HttpAction action) + { + if ( ! action.getDatasetRef().allowDatasetUpdate ) + errorMethodNotAllowed("POST") ; + + // Graph Store Protocol mode - POST triples to dataset causes + // a new graph to be created and the new URI returned via Location. + // Normally off. + // When off, POST of triples goes to default graph. + boolean gspMode = Fuseki.graphStoreProtocolPostCreate ; + + // Code to pass the GSP test suite. + // Not necessarily good code. + String x = action.request.getContentType() ; + if ( x == null ) + errorBadRequest("Content-type required for data format") ; + + MediaType mediaType = MediaType.create(x) ; + Lang lang = RDFLanguages.contentTypeToLang(mediaType.getContentType()) ; + if ( lang == null ) + lang = RDFLanguages.TRIG ; + + if ( action.verbose ) + log.info(format("[%d] Post: Content-Type=%s, Charset=%s => %s", + action.id, mediaType.getContentType(), mediaType.getCharset(), lang.getName())) ; + + if ( RDFLanguages.isQuads(lang) ) + doPostQuads(action, lang) ; + else if ( gspMode && RDFLanguages.isTriples(lang) ) + doPostTriplesGSP(action, lang) ; + else if ( RDFLanguages.isTriples(lang) ) + doPostTriples(action, lang) ; + else + errorBadRequest("Not a triples or quads format: "+mediaType) ; + } + + protected void doPostQuads(HttpAction action, Lang lang) + { + action.beginWrite() ; + try { + String name = action.request.getRequestURL().toString() ; + DatasetGraph dsg = action.getActiveDSG() ; + StreamRDF dest = StreamRDFLib.dataset(dsg) ; + ReaderRIOT reader = RDFDataMgr.createReader(lang) ; + reader.read(action.request.getInputStream(), name, null, dest, null); + action.commit(); + success(action) ; + } catch (IOException ex) { action.abort() ; } + finally { action.endWrite() ; } + } + + + // POST triples to dataset -- send to default graph. + protected void doPostTriples(HttpAction action, Lang lang) + { + action.beginWrite() ; + try { + DatasetGraph dsg = action.getActiveDSG() ; + // This should not be anythign other than the datasets name via this route. + String name = action.request.getRequestURL().toString() ; + //log.info(format("[%d] ** Content-length: %d", action.id, action.request.getContentLength())) ; + Graph g = dsg.getDefaultGraph() ; + StreamRDF dest = StreamRDFLib.graph(g) ; + ReaderRIOT reader = RDFDataMgr.createReader(lang) ; + reader.read(action.request.getInputStream(), name, null, dest, null); + action.commit(); + success(action) ; + } catch (IOException ex) { action.abort() ; } + finally { action.endWrite() ; } + } + + protected void doPostTriplesGSP(HttpAction action, Lang lang) + { + action.beginWrite() ; + try { + DatasetGraph dsg = action.getActiveDSG() ; + //log.info(format("[%d] ** Content-length: %d", action.id, action.request.getContentLength())) ; + + String name = action.request.getRequestURL().toString() ; + if ( ! name.endsWith("/") ) + name = name+ "/" ; + name = name+(++counter) ; + Node gn = NodeFactory.createURI(name) ; + Graph g = dsg.getGraph(gn) ; + StreamRDF dest = StreamRDFLib.graph(g) ; + ReaderRIOT reader = RDFDataMgr.createReader(lang) ; + reader.read(action.request.getInputStream(), name, null, dest, null); + log.info(format("[%d] Location: %s", action.id, name)) ; + action.response.setHeader("Location", name) ; + action.commit(); + successCreated(action) ; + } catch (IOException ex) { action.abort() ; } + finally { action.endWrite() ; } + } + + @Override + protected void doDelete(HttpAction action) + { errorMethodNotAllowed("DELETE") ; } + + @Override + protected void doPut(HttpAction action) + { errorMethodNotAllowed("PUT") ; } + + @Override + protected void doPatch(HttpAction action) + { errorMethodNotAllowed("PATCH") ; } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseCallback.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseCallback.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseCallback.java new file mode 100644 index 0000000..1a78627 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseCallback.java @@ -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.servlets ; + +public interface ResponseCallback +{ + public void callback(boolean successfulOperation) ; +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java new file mode 100644 index 0000000..f2172f0 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java @@ -0,0 +1,143 @@ +/* + * 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 org.apache.jena.fuseki.servlets.ServletBase.error ; +import static org.apache.jena.fuseki.servlets.ServletBase.errorBadRequest ; +import static org.apache.jena.fuseki.servlets.ServletBase.errorOccurred ; + +import java.util.HashMap ; +import java.util.Map ; + +import javax.servlet.ServletOutputStream ; +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.Fuseki ; +import org.apache.jena.fuseki.conneg.ConNeg ; +import org.apache.jena.fuseki.conneg.WebLib ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.riot.RDFLanguages ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.web.HttpSC ; +import org.slf4j.Logger ; + +import com.hp.hpl.jena.rdf.model.Model ; + +public class ResponseModel +{ + private static Logger slog = ServletBase.log ; + + // Short names for "output=" + private static final String contentOutputJSONLD = "json-ld" ; + private static final String contentOutputJSONRDF = "json-rdf" ; + private static final String contentOutputJSON = "json" ; + private static final String contentOutputXML = "xml" ; + private static final String contentOutputText = "text" ; + private static final String contentOutputTTL = "ttl" ; + private static final String contentOutputNT = "nt" ; + + public static Map<String,String> shortNamesModel = new HashMap<String, String>() ; + static { + + // Some short names. keys are lowercase. + ResponseOps.put(shortNamesModel, contentOutputJSONLD, WebContent.contentTypeJSONLD) ; + ResponseOps.put(shortNamesModel, contentOutputJSONRDF, WebContent.contentTypeRDFJSON) ; + ResponseOps.put(shortNamesModel, contentOutputJSON, WebContent.contentTypeJSONLD) ; + ResponseOps.put(shortNamesModel, contentOutputXML, WebContent.contentTypeRDFXML) ; + ResponseOps.put(shortNamesModel, contentOutputText, WebContent.contentTypeTurtle) ; + ResponseOps.put(shortNamesModel, contentOutputTTL, WebContent.contentTypeTurtle) ; + ResponseOps.put(shortNamesModel, contentOutputNT, WebContent.contentTypeNTriples) ; + } + + public static void doResponseModel(HttpAction action, Model model) + { + HttpServletRequest request = action.request ; + HttpServletResponse response = action.response ; + + String mimeType = null ; // Header request type + + // TODO Use MediaType throughout. + MediaType i = ConNeg.chooseContentType(request, DEF.rdfOffer, DEF.acceptRDFXML) ; + if ( i != null ) + mimeType = i.getContentType() ; + + String outputField = ResponseOps.paramOutput(request, shortNamesModel) ; + if ( outputField != null ) + mimeType = outputField ; + + String writerMimeType = mimeType ; + + if ( mimeType == null ) + { + Fuseki.requestLog.warn("Can't find MIME type for response") ; + String x = WebLib.getAccept(request) ; + String msg ; + if ( x == null ) + msg = "No Accept: header" ; + else + msg = "Accept: "+x+" : Not understood" ; + error(HttpSC.NOT_ACCEPTABLE_406, msg) ; + } + + String contentType = mimeType ; + String charset = WebContent.charsetUTF8 ; + + String forceAccept = ResponseOps.paramForceAccept(request) ; + if ( forceAccept != null ) + { + contentType = forceAccept ; + charset = WebContent.charsetUTF8 ; + } + + Lang lang = RDFLanguages.contentTypeToLang(contentType) ; + if ( lang == null ) + errorBadRequest("Can't determine output content type: "+contentType) ; + +// if ( rdfw instanceof RDFXMLWriterI ) +// rdfw.setProperty("showXmlDeclaration", "true") ; + + // // Write locally to check it's possible. + // // Time/space tradeoff. + // try { + // OutputStream out = new NullOutputStream() ; + // RDFDataMgr.write(out, model, lang) ; + // IO.flush(out) ; + // } catch (JenaException ex) + // { + // SPARQL_ServletBase.errorOccurred(ex) ; + // } + + try { + ResponseResultSet.setHttpResponse(request, response, contentType, charset) ; + response.setStatus(HttpSC.OK_200) ; + ServletOutputStream out = response.getOutputStream() ; + RDFDataMgr.write(out, model, lang) ; + out.flush() ; + } + catch (Exception ex) { + slog.info("Exception while writing the response model: "+ex.getMessage(), ex) ; + errorOccurred("Exception while writing the response model: "+ex.getMessage(), ex) ; + } + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseOps.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseOps.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseOps.java new file mode 100644 index 0000000..62ad6d5 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseOps.java @@ -0,0 +1,94 @@ +/* + * 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.util.Locale ; +import java.util.Map ; + +import javax.servlet.http.HttpServletRequest ; + +import org.apache.jena.fuseki.HttpNames ; + +public class ResponseOps +{ + // Helpers + public static void put(Map<String, String> map, String key, String value) + { + map.put(key.toLowerCase(Locale.ROOT), value) ; + } + + public static boolean isEOFexception(IOException ioEx) + { + if ( ioEx.getClass().getName().equals("org.mortbay.jetty.EofException eofEx") ) + return true ; + if ( ioEx instanceof java.io.EOFException ) + return true ; + return false ; + } + + public static String paramForceAccept(HttpServletRequest request) + { + String x = fetchParam(request, HttpNames.paramForceAccept) ; + return x ; + } + + public static String paramStylesheet(HttpServletRequest request) + { return fetchParam(request, HttpNames.paramStyleSheet) ; } + + public static String paramOutput(HttpServletRequest request, Map<String,String> map) + { + // Two names. + String x = fetchParam(request, HttpNames.paramOutput1) ; + if ( x == null ) + x = fetchParam(request, HttpNames.paramOutput2) ; + return expandShortName(x, map) ; + } + + public static String expandShortName(String str, Map<String,String> map) + { + if ( str == null ) + return null ; + // Force keys to lower case. See put() above. + String key = str.toLowerCase(Locale.ROOT) ; + String str2 = map.get(key) ; + if ( str2 == null ) + return str ; + return str2 ; + } + + public static String paramCallback(HttpServletRequest request) + { + return fetchParam(request, HttpNames.paramCallback) ; + } + + public static String fetchParam(HttpServletRequest request, String parameterName) + { + String value = request.getParameter(parameterName) ; + if ( value != null ) + { + value = value.trim() ; + if ( value.length() == 0 ) + value = null ; + } + return value ; + } + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java new file mode 100644 index 0000000..c42378b --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java @@ -0,0 +1,320 @@ +/* + * 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.atlas.lib.Lib.equal ; +import static org.apache.jena.fuseki.servlets.ServletBase.errorBadRequest ; +import static org.apache.jena.fuseki.servlets.ServletBase.errorOccurred ; +import static org.apache.jena.fuseki.servlets.ServletBase.log ; + +import java.io.IOException ; +import java.util.HashMap ; +import java.util.Map ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.commons.lang.StringUtils ; +import org.apache.jena.atlas.web.AcceptList ; +import org.apache.jena.atlas.web.MediaType ; +import org.apache.jena.fuseki.DEF ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.conneg.ConNeg ; +import org.apache.jena.riot.ResultSetMgr ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.riot.resultset.ResultSetLang ; +import org.apache.jena.web.HttpSC ; +import org.slf4j.Logger ; +import org.slf4j.LoggerFactory ; + +import com.hp.hpl.jena.query.QueryCancelledException ; +import com.hp.hpl.jena.query.ResultSet ; +import com.hp.hpl.jena.query.ResultSetFormatter ; +import com.hp.hpl.jena.sparql.core.Prologue ; + +/** This is the content negotiation for each kind of SPARQL query result */ +public class ResponseResultSet +{ + private static Logger xlog = LoggerFactory.getLogger(ResponseResultSet.class) ; + private static Logger slog = ServletBase.log ; + + // Short names for "output=" + private static final String contentOutputJSON = "json" ; + private static final String contentOutputXML = "xml" ; + private static final String contentOutputSPARQL = "sparql" ; + private static final String contentOutputText = "text" ; + private static final String contentOutputCSV = "csv" ; + private static final String contentOutputTSV = "tsv" ; + private static final String contentOutputThrift = "thrift" ; + + public static Map<String,String> shortNamesResultSet = new HashMap<String, String>() ; + static { + // Some short names. keys are lowercase. + ResponseOps.put(shortNamesResultSet, contentOutputJSON, WebContent.contentTypeResultsJSON) ; + ResponseOps.put(shortNamesResultSet, contentOutputSPARQL, WebContent.contentTypeResultsXML) ; + ResponseOps.put(shortNamesResultSet, contentOutputXML, WebContent.contentTypeResultsXML) ; + ResponseOps.put(shortNamesResultSet, contentOutputText, WebContent.contentTypeTextPlain) ; + ResponseOps.put(shortNamesResultSet, contentOutputCSV, WebContent.contentTypeTextCSV) ; + ResponseOps.put(shortNamesResultSet, contentOutputTSV, WebContent.contentTypeTextTSV) ; + ResponseOps.put(shortNamesResultSet, contentOutputThrift, WebContent.contentTypeResultsThrift) ; + } + + interface OutputContent { void output(ServletOutputStream out) ; } + + public static void doResponseResultSet(HttpAction action, Boolean booleanResult) + { + doResponseResultSet$(action, null, booleanResult, null, DEF.rsOfferTable) ; + } + + public static void doResponseResultSet(HttpAction action, ResultSet resultSet, Prologue qPrologue) + { + doResponseResultSet$(action, resultSet, null, qPrologue, DEF.rsOfferTable) ; + } + + // If we refactor the conneg into a single function, we can split boolean and result set handling. + + // One or the other argument must be null + private static void doResponseResultSet$(HttpAction action, + ResultSet resultSet, Boolean booleanResult, + Prologue qPrologue, + AcceptList contentTypeOffer) + { + HttpServletRequest request = action.request ; + HttpServletResponse response = action.response ; + long id = action.id ; + + if ( resultSet == null && booleanResult == null ) + { + xlog.warn("doResponseResult: Both result set and boolean result are null") ; + throw new FusekiException("Both result set and boolean result are null") ; + } + + if ( resultSet != null && booleanResult != null ) + { + xlog.warn("doResponseResult: Both result set and boolean result are set") ; + throw new FusekiException("Both result set and boolean result are set") ; + } + + String mimeType = null ; + MediaType i = ConNeg.chooseContentType(request, contentTypeOffer, DEF.acceptRSXML) ; + if ( i != null ) + mimeType = i.getContentType() ; + + // Override content type + // Does &output= override? + // Requested output type by the web form or &output= in the request. + String outputField = ResponseOps.paramOutput(request, shortNamesResultSet) ; // Expands short names + if ( outputField != null ) + mimeType = outputField ; + + String serializationType = mimeType ; // Choose the serializer based on this. + String contentType = mimeType ; // Set the HTTP respose header to this. + + // Stylesheet - change to application/xml. + final String stylesheetURL = ResponseOps.paramStylesheet(request) ; + if ( stylesheetURL != null && equal(serializationType,WebContent.contentTypeResultsXML) ) + contentType = WebContent.contentTypeXML ; + + // Force to text/plain? + String forceAccept = ResponseOps.paramForceAccept(request) ; + if ( forceAccept != null ) + contentType = WebContent.contentTypeTextPlain ; + + // Better : dispatch on MediaType + // Fuseki2 uses the SPARQL parser/write registry. + if ( equal(serializationType, WebContent.contentTypeResultsXML) ) + sparqlXMLOutput(action, contentType, resultSet, stylesheetURL, booleanResult) ; + else if ( equal(serializationType, WebContent.contentTypeResultsJSON) ) + jsonOutput(action, contentType, resultSet, booleanResult) ; + else if ( equal(serializationType, WebContent.contentTypeTextPlain) ) + textOutput(action, contentType, resultSet, qPrologue, booleanResult) ; + else if ( equal(serializationType, WebContent.contentTypeTextCSV) ) + csvOutput(action, contentType, resultSet, booleanResult) ; + else if (equal(serializationType, WebContent.contentTypeTextTSV) ) + tsvOutput(action, contentType, resultSet, booleanResult) ; + else if (equal(serializationType, WebContent.contentTypeResultsThrift) ) + thriftOutput(action, contentType, resultSet, booleanResult) ; + else + errorBadRequest("Can't determine output serialization: "+serializationType) ; + } + + + public static void setHttpResponse(HttpServletRequest httpRequest, + HttpServletResponse httpResponse, + String contentType, String charset) + { + // ---- Set up HTTP Response + // Stop caching (not that ?queryString URLs are cached anyway) + if ( true ) + { + httpResponse.setHeader("Cache-Control", "no-cache") ; + httpResponse.setHeader("Pragma", "no-cache") ; + } + // See: http://www.w3.org/International/O-HTTP-charset.html + if ( contentType != null ) + { + if ( charset != null && ! isXML(contentType) ) + contentType = contentType+"; charset="+charset ; + log.trace("Content-Type for response: "+contentType) ; + httpResponse.setContentType(contentType) ; + } + } + + private static boolean isXML(String contentType) + { + return contentType.equals(WebContent.contentTypeRDFXML) + || contentType.equals(WebContent.contentTypeResultsXML) + || contentType.equals(WebContent.contentTypeXML) ; + } + + private static void sparqlXMLOutput(HttpAction action, String contentType, final ResultSet resultSet, final String stylesheetURL, final Boolean booleanResult) + { + OutputContent proc = + new OutputContent(){ + @Override + public void output(ServletOutputStream out) + { + if ( resultSet != null ) + ResultSetFormatter.outputAsXML(out, resultSet, stylesheetURL) ; + if ( booleanResult != null ) + ResultSetFormatter.outputAsXML(out, booleanResult, stylesheetURL) ; + }} ; + output(action, contentType, null, proc) ; + } + + private static void jsonOutput(HttpAction action, String contentType, final ResultSet resultSet, final Boolean booleanResult) + { + OutputContent proc = new OutputContent(){ + @Override + public void output(ServletOutputStream out) + { + if ( resultSet != null ) + ResultSetFormatter.outputAsJSON(out, resultSet) ; + if ( booleanResult != null ) + ResultSetFormatter.outputAsJSON(out, booleanResult ) ; + } + } ; + + try { + String callback = ResponseOps.paramCallback(action.request) ; + ServletOutputStream out = action.response.getOutputStream() ; + + if ( callback != null ) + { + callback = StringUtils.replaceChars(callback, "\r", "") ; + callback = StringUtils.replaceChars(callback, "\n", "") ; + out.print(callback) ; + out.println("(") ; + } + + output(action, contentType, WebContent.charsetUTF8, proc) ; + + if ( callback != null ) + out.println(")") ; + } catch (IOException ex) { errorOccurred(ex) ; } + } + + private static void textOutput(HttpAction action, String contentType, final ResultSet resultSet, final Prologue qPrologue, final Boolean booleanResult) + { + // Text is not streaming. + OutputContent proc = new OutputContent(){ + @Override + public void output(ServletOutputStream out) + { + if ( resultSet != null ) + ResultSetFormatter.out(out, resultSet, qPrologue) ; + if ( booleanResult != null ) + ResultSetFormatter.out(out, booleanResult ) ; + } + }; + + output(action, contentType, WebContent.charsetUTF8, proc) ; + } + + private static void csvOutput(HttpAction action, String contentType, final ResultSet resultSet, final Boolean booleanResult) { + OutputContent proc = new OutputContent(){ + @Override + public void output(ServletOutputStream out) + { + if ( resultSet != null ) + ResultSetFormatter.outputAsCSV(out, resultSet) ; + if ( booleanResult != null ) + ResultSetFormatter.outputAsCSV(out, booleanResult ) ; + } + } ; + output(action, contentType, WebContent.charsetUTF8, proc) ; + } + + private static void tsvOutput(HttpAction action, String contentType, final ResultSet resultSet, final Boolean booleanResult) { + OutputContent proc = new OutputContent(){ + @Override + public void output(ServletOutputStream out) + { + if ( resultSet != null ) + ResultSetFormatter.outputAsTSV(out, resultSet) ; + if ( booleanResult != null ) + ResultSetFormatter.outputAsTSV(out, booleanResult ) ; + } + } ; + output(action, contentType, WebContent.charsetUTF8, proc) ; + } + + private static void thriftOutput(HttpAction action, String contentType, final ResultSet resultSet, final Boolean booleanResult) { + OutputContent proc = new OutputContent(){ + @Override + public void output(ServletOutputStream out) + { + if ( resultSet != null ) + ResultSetMgr.write(out, resultSet, ResultSetLang.SPARQLResultSetThrift) ; + if ( booleanResult != null ) + slog.error("Can't write boolen result in thrift") ; + } + } ; + output(action, contentType, WebContent.charsetUTF8, proc) ; + } + + private static void output(HttpAction action, String contentType, String charset, OutputContent proc) + { + try { + setHttpResponse(action.request, action.response, contentType, charset) ; + action.response.setStatus(HttpSC.OK_200) ; + ServletOutputStream out = action.response.getOutputStream() ; + try + { + proc.output(out) ; + out.flush() ; + } catch (QueryCancelledException ex) { + // Bother. Status code 200 already sent. + slog.info(format("[%d] Query Cancelled - results truncated (but 200 already sent)", action.id)) ; + out.println() ; + out.println("## Query cancelled due to timeout during execution ##") ; + out.println("## **** Incomplete results **** ##") ; + out.flush() ; + // No point raising an exception - 200 was sent already. + //errorOccurred(ex) ; + } + // Includes client gone. + } catch (IOException ex) + { errorOccurred(ex) ; } + // Do not call httpResponse.flushBuffer(); here - Jetty closes the stream if it is a gzip stream + // then the JSON callback closing details can't be added. + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Protocol.java ---------------------------------------------------------------------- diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Protocol.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Protocol.java new file mode 100644 index 0000000..ed57a37 --- /dev/null +++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Protocol.java @@ -0,0 +1,101 @@ +/** + * 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 org.apache.jena.fuseki.HttpNames.paramDefaultGraphURI ; +import static org.apache.jena.fuseki.HttpNames.paramNamedGraphURI ; + +import java.util.Arrays ; +import java.util.Collections ; +import java.util.List ; + +import javax.servlet.http.HttpServletRequest ; + +import org.apache.jena.atlas.iterator.Filter ; +import org.apache.jena.atlas.iterator.Iter ; +import org.apache.jena.atlas.lib.Lib ; + +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.query.QueryParseException ; +import com.hp.hpl.jena.sparql.core.DatasetDescription ; + +/** Support for the SPARQL protocol (SPARQL Query, SPARQL Update) + */ +public abstract class SPARQL_Protocol extends SPARQL_ServletBase +{ + protected SPARQL_Protocol() { super() ; } + + protected static String messageForQPE(QueryParseException ex) + { + if ( ex.getMessage() != null ) + return ex.getMessage() ; + if ( ex.getCause() != null ) + return Lib.classShortName(ex.getCause().getClass()) ; + return null ; + } + + protected static DatasetDescription getDatasetDescription(HttpAction action) + { + List<String> graphURLs = toStrList(action.request.getParameterValues(paramDefaultGraphURI)) ; + List<String> namedGraphs = toStrList(action.request.getParameterValues(paramNamedGraphURI)) ; + + graphURLs = removeEmptyValues(graphURLs) ; + namedGraphs = removeEmptyValues(namedGraphs) ; + + if ( graphURLs.size() == 0 && namedGraphs.size() == 0 ) + return null ; + return DatasetDescription.create(graphURLs, namedGraphs) ; + } + + protected static DatasetDescription getDatasetDescription(Query query) + { + return DatasetDescription.create(query) ; + } + + private static List<String> toStrList(String[] array) + { + if ( array == null ) + return Collections.emptyList() ; + return Arrays.asList(array) ; + } + + private static List<String> removeEmptyValues(List<String> list) + { + return Iter.iter(list).filter(acceptNonEmpty).toList() ; + } + + private static Filter<String> acceptNonEmpty = new Filter<String>(){ + @Override + public boolean accept(String item) + { + return item != null && item.length() != 0 ; + } + } ; + + protected static int countParamOccurences(HttpServletRequest request, String param) + { + String[] x = request.getParameterValues(param) ; + if ( x == null ) + return 0 ; + return x.length ; + } + + +} +
