package org.basex.http.restxq;
import static org.basex.http.restxq.RestXqText.*;
import static org.basex.util.Token.*;
import java.io.*;
import org.basex.http.*;
import org.basex.io.out.*;
import org.basex.io.serial.*;
import org.basex.query.*;
import org.basex.query.expr.*;
import org.basex.query.func.*;
import org.basex.query.iter.*;
import org.basex.query.scope.*;
import org.basex.query.value.item.*;
import org.basex.query.value.node.*;
import org.basex.query.value.type.*;
import org.basex.util.http.*;
/**
* This class creates a new HTTP response.
*
* @author BaseX Team 2005-17, BSD License
* @author Christian Gruen
*/
final class RestXqResponse {
/** Function. */
final RestXqFunction func;
/** Query context. */
final QueryContext qc;
/** HTTP connection. */
final HTTPConnection conn;
/** Output stream. */
private OutputStream out;
/** Status message. */
private String message;
/** Status code. */
private Integer status;
/**
* Constructor.
* @param func function
* @param qc query context
* @param conn HTTP connection
*/
RestXqResponse(final RestXqFunction func, final QueryContext qc, final HTTPConnection conn) {
this.func = func;
this.qc = qc;
this.conn = conn;
}
/**
* Evaluates the specified function and serializes the result.
* @param qe query exception (optional)
* @throws Exception exception (including unexpected ones)
*/
void create(final QueryException qe) throws Exception {
// bind variables
final StaticFunc sf = func.function;
final Expr[] args = new Expr[sf.args.length];
func.bind(conn, args, qe, qc);
// assign function call and http context and register process
qc.mainModule(MainModule.get(sf, args));
qc.http(conn);
qc.jc().type(RESTXQ);
final String singleton = func.singleton;
final RestXqSession session = new RestXqSession(conn, singleton, qc);
String redirect = null, forward = null;
qc.register(qc.context);
try {
// evaluate query
final Iter iter = qc.iter();
// handle response element
final Item first = iter.next();
if(first instanceof ANode) {
final ANode node = (ANode) first;
if(REST_REDIRECT.eq(node)) {
// send redirect to browser
final ANode ch = node.children().next();
if(ch == null || ch.type != NodeType.TXT) throw func.error(NO_VALUE, node.name());
redirect = string(ch.string()).trim();
} else if(REST_FORWARD.eq(node)) {
// server-side forwarding
final ANode ch = node.children().next();
if(ch == null || ch.type != NodeType.TXT) throw func.error(NO_VALUE, node.name());
forward = string(ch.string()).trim();
} else if(REST_RESPONSE.eq(node)) {
// custom response
build(node, iter);
} else {
// standard serialization
serialize(first, iter, false);
}
} else if(singleton != null) {
// cached serialization
serialize(first, iter, true);
} else {
// standard serialization
serialize(first, iter, false);
}
} finally {
qc.close();
qc.unregister(qc.context);
session.close();
if(redirect != null) {
conn.redirect(redirect);
} else if(forward != null) {
conn.forward(forward);
} else {
qc.checkStop();
finish();
}
}
}
/**
* Builds a response element and creates the serialization parameters.
* @param response response element
* @param iter result iterator
* @throws Exception exception (including unexpected ones)
*/
private void build(final ANode response, final Iter iter) throws Exception {
// don't allow attributes
for(final ANode a : response.attributes()) throw func.error(UNEXP_NODE, a);
// parse response and serialization parameters
SerializerOptions sp = func.output;
String cType = null;
for(final ANode n : response.children()) {
// process http:response element
if(HTTP_RESPONSE.eq(n)) {
// check status and reason
byte[] sta = null, msg = null;
for(final ANode a : n.attributes()) {
final QNm qnm = a.qname();
if(qnm.eq(Q_STATUS)) sta = a.string();
else if(qnm.eq(Q_REASON) || qnm.eq(Q_MESSAGE)) msg = a.string();
else throw func.error(UNEXP_NODE, a);
}
if(sta != null) {
status = toInt(sta);
message = msg != null ? string(msg) : null;
}
for(final ANode c : n.children()) {
// process http:header elements
if(HTTP_HEADER.eq(c)) {
final byte[] nam = c.attribute(Q_NAME);
final byte[] val = c.attribute(Q_VALUE);
if(nam != null && val != null) {
final String key = string(nam), value = string(val);
if(key.equalsIgnoreCase(HttpText.CONTENT_TYPE)) {
cType = value;
} else {
conn.res.setHeader(key, key.equalsIgnoreCase(HttpText.LOCATION) ?
conn.resolve(value) : value);
}
}
} else {
throw func.error(UNEXP_NODE, c);
}
}
} else if(OUTPUT_SERIAL.eq(n)) {
// parse output:serialization-parameters
sp = FuncOptions.serializer(n, func.output, func.function.info);
} else {
throw func.error(UNEXP_NODE, n);
}
}
// set content type and serialize data
if(cType != null) sp.set(SerializerOptions.MEDIA_TYPE, cType);
final Item first = iter.next();
if(first != null) checkHead();
serialize(first, iter, sp, true);
}
/**
* Serializes the first and all remaining items.
* @param first first item
* @param iter iterator
* @param cache cache result
* @throws Exception exception (including unexpected ones)
*/
private void serialize(final Item first, final Iter iter, final boolean cache) throws Exception {
checkHead();
serialize(first, iter, func.output, cache);
}
/**
* Serializes the first and all remaining items.
* @param first first item
* @param iter iterator
* @param sp serialization parameters
* @param cache cache result
* @throws Exception exception (including unexpected ones)
*/
private void serialize(final Item first, final Iter iter, final SerializerOptions sp,
final boolean cache) throws Exception {
conn.sopts(sp);
conn.initResponse();
out = cache ? new ArrayOutput() : conn.res.getOutputStream();
Item item = first;
try(Serializer ser = Serializer.get(out, sp)) {
for(; item != null; item = iter.next()) {
qc.checkStop();
ser.serialize(item);
}
}
}
/**
* Checks if the HEAD method was specified.
* @throws QueryException query exception
*/
private void checkHead() throws QueryException {
if(func.methods.size() == 1 && func.methods.contains(HttpMethod.HEAD.name()))
throw func.error(HEAD_METHOD);
}
/**
* Finalizes result generation.
* @throws IOException I/O exception
*/
private void finish() throws IOException {
if(status != null) conn.status(status, message);
if(out instanceof ArrayOutput) {
final ArrayOutput ao = (ArrayOutput) out;
if(ao.size() > 0) conn.res.getOutputStream().write(ao.finish());
}
}
}