![JAR search and dependency download from the Maven repository](/logo.png)
com.bigdata.rdf.sail.webapp.AbstractRestApiTask Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.rdf.sail.webapp;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.bigdata.journal.AbstractTask;
import com.bigdata.rdf.sparql.ast.QueryHints;
import com.bigdata.rdf.task.AbstractApiTask;
import com.bigdata.util.NV;
/**
* Abstract base class for REST API methods. This class is compatible with a
* job-oriented concurrency control pattern.
*
* @author Bryan Thompson
* @param
* @see HA
* doLocalAbort() should interrupt NSS requests and AbstractTasks
* @see
* Concurrent unisolated operations against multiple KBs
*/
public abstract class AbstractRestApiTask extends AbstractApiTask {
private static final Logger log = Logger.getLogger(AbstractRestApiTask.class);
/**
* The {@link UUID} associated with this task. This is used to CANCEL a
* task.
*
* @see All REST API
* operations should be cancelable from both REST API and workbench
*
*/
protected final UUID uuid;
/** The {@link HttpServletRequest}. */
protected final HttpServletRequest req;
/** The {@link HttpServletResponse}. */
protected final HttpServletResponse resp;
/**
* The {@link ServletOutputStream} iff requested by the task.
*/
private ServletOutputStream os;
/**
* The {@link PrintWriter} iff requested by the task.
*/
private PrintWriter writer;
/**
* Used to coordinate access to the {@link ServletOutputStream} versus the
* {@link PrintWriter} (visibility).
*/
private final Lock lock = new ReentrantLock();
/**
* Return the {@link ServletOutputStream} associated with the request (and
* stash a copy).
*
* @throws IOException
* @throws IllegalStateException
* per the servlet API if the writer has been requested already.
*/
public OutputStream getOutputStream() throws IOException {
lock.lock();
try {
return new FilterOutputStream(this.os = resp.getOutputStream()) {
@Override
public void flush() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
throw new UnsupportedOperationException();
}
};
} finally {
lock.unlock();
}
}
/**
* Return the {@link PrintWriter} associated with the request (and stash a
* copy).
*
* @throws IOException
* @throws IllegalStateException
* per the servlet API if the {@link ServletOutputStream} has
* been requested already.
*/
public PrintWriter getWriter() throws IOException {
lock.lock();
try {
return new PrintWriter(this.writer = resp.getWriter()) {
@Override
public void flush() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
throw new UnsupportedOperationException();
}
};
} finally {
lock.unlock();
}
}
/**
* Flush and close the {@link ServletOutputStream} or {@link PrintWriter}
* depending on which was obtained.
*
* @throws IOException
*/
public void flushAndClose() throws IOException {
lock.lock();
try {
if(log.isInfoEnabled())
log.info("FLUSH-AND-CLOSE: " + toString());
if (os != null) {
final ServletOutputStream tmp = resp.getOutputStream();
tmp.flush();
tmp.close();
} else if (writer != null) {
final PrintWriter tmp = resp.getWriter();
tmp.flush();
tmp.close();
}
} finally {
lock.unlock();
}
}
/**
* @param req
* The {@link HttpServletRequest}.
* @param resp
* The {@link HttpServletResponse}.
* @param namespace
* The namespace of the target KB instance.
* @param timestamp
* The timestamp of the view of that KB instance.
*/
protected AbstractRestApiTask(final HttpServletRequest req,
final HttpServletResponse resp, final String namespace,
final long timestamp) {
this(req, resp, namespace,timestamp, false/*isGSRRequired*/);
}
/**
* @param req
* The {@link HttpServletRequest}.
* @param resp
* The {@link HttpServletResponse}.
* @param namespace
* The namespace of the target KB instance.
* @param timestamp
* The timestamp of the view of that KB instance.
* @param isGSRRequired
* true
iff the task requires a lock on the GRS
* index.
*/
protected AbstractRestApiTask(final HttpServletRequest req,
final HttpServletResponse resp, final String namespace,
final long timestamp, final boolean isGRSRequired) {
super(namespace, timestamp, isGRSRequired);
this.req = req;
this.resp = resp;
// Extract the UUID of the request (if given).
final String s = req.getParameter(QueryHints.QUERYID);
// The given UUID or a randomly assigned one.
this.uuid = s == null ? UUID.randomUUID() : UUID.fromString(s);
}
@Override
public String toString() {
return getClass().getSimpleName() + "{namespace=" + getNamespace()
+ ",timestamp=" + getTimestamp() + ", isGRSRequired="
+ isGRSRequired() + "}";
}
/**
* Reports the mutation count and elapsed operation time as specified by the
* REST API for mutation operations.
*
* Note: When GROUP_COMMIT (#566) is enabled the http output stream MUST NOT
* be closed by this method. Doing so would permit the client to conclude
* that the operation was finished before the group commit actually occurs.
* This situation arises because the AbstractApiTask implementation has
* invoked conn.commit() and hence believes that it was successful, but the
* {@link AbstractTask} itself has not yet been through a checkpoint and the
* write set for the commit group has not yet been melded into a stable
* commit point. By NOT closing the output stream here we defer the signal
* to the client until control is returned to the servlet container layer,
* which does not occur until the task has finished executing and the group
* commit is done. The servlet container then closes the output stream and
* the client notices that the mutation operation is done and that its
* applied mutation is now visible to a client reading against that commit
* point. (Mind you, there might be other mutations in the same group commit
* or even in subsequent commits so the client does not have a strong
* guarantee that their mutation is visible in a post-commit read unless
* they are controlling all writers.)
*
* Note: The other code path for signaling to the client that a mutation is
* done is SPARQL UPDATE. See that code for a similar caution.
*
* @param nmodified
* The number of modified triples/quads.
* @param elapsed
* The elapsed time for the operation.
*
* @throws IOException
*/
protected void reportModifiedCount(final long nmodified, final long elapsed)
throws IOException {
if (log.isInfoEnabled())
log.info("task=" + this + ", nmodified=" + nmodified);
final StringWriter stringWriter = new StringWriter();
final XMLBuilder t = new XMLBuilder(stringWriter);
t.root("data").attr("modified", nmodified)
.attr("milliseconds", elapsed).close();
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType(BigdataServlet.MIME_APPLICATION_XML);
// buildResponse(resp, HTTP_OK, MIME_APPLICATION_XML, w.toString());
final Writer writer = resp.getWriter();
writer.write(stringWriter.toString());
/*
* Note: GROUP COMMIT: This would flush the response to the client. This
* MUST NOT be invoked before the group commit point! See flushAndClose().
*/
// w.flush();
}
/**
* Generate a response having the indicated http status code, mime type, and
* content.
*
* @param status
* The http status code.
* @param mimeType
* The MIME type of the response.
* @param content
* The content
* @param headers
* Zero or more headers to be included in the response.
*
* @throws IOException
*
* @throws AssertionError
* if the caller attempts to use this method for a non-success
* (2xx) outcome. The correct pattern is to throw an
* {@link HttpOperationException} instead once you are inside of
* an {@link AbstractRestApiTask}.
*/
protected void buildResponse(final int status, final String mimeType,
final String content,final NV...headers) throws IOException {
if (status < 200 || status >= 300)
throw new AssertionError(
"This method must not be used for non-success outcomes");
if (log.isInfoEnabled())
log.info("task=" + this + ", status=" + status + ", mimeType="
+ mimeType + ", content=[" + content + "]");
resp.setStatus(status);
resp.setContentType(mimeType);
for (NV nv : headers) {
resp.setHeader(nv.getName(), nv.getValue());
}
final Writer w = resp.getWriter();
if (content != null)
w.write(content);
/*
* Note: GROUP COMMIT: This would flush the response to the client. This
* MUST NOT be invoked before the group commit point! See flushAndClose().
*/
// w.flush();
}
}