/*
* @(#)$Id: QueryProcessor.java 3619 2008-03-26 07:23:03Z yui $
*
* Copyright 2006-2008 Makoto YUI
*
* Licensed 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.
*
* Contributors:
* Makoto YUI - initial implementation
*/
package xbird.engine.backend;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import xbird.engine.Request;
import xbird.engine.RequestContext;
import xbird.engine.ResponseListener;
import xbird.engine.Request.ReturnType;
import xbird.engine.Request.Signature;
import xbird.engine.remote.RemoteSequence;
import xbird.engine.remote.RemoteSequenceProxy;
import xbird.engine.remote.RunnableRemoteSequenceProxy;
import xbird.engine.remote.ThrottedRemoteSequenceProxy;
import xbird.engine.request.QueryRequest;
import xbird.util.concurrent.ExecutorFactory;
import xbird.util.concurrent.collections.ConcurrentIdentityHashMap;
import xbird.util.net.TimeoutSocketProdiver;
import xbird.xquery.XQueryException;
import xbird.xquery.XQueryModule;
import xbird.xquery.XQueryProcessor;
import xbird.xquery.dm.value.Item;
import xbird.xquery.dm.value.Sequence;
import xbird.xquery.dm.value.sequence.MarshalledSequence;
import xbird.xquery.dm.value.sequence.ValueSequence;
import xbird.xquery.expr.XQExpression;
import xbird.xquery.expr.ext.BDQExpr;
import xbird.xquery.expr.func.DirectFunctionCall;
import xbird.xquery.expr.opt.ShippedVariable;
import xbird.xquery.expr.var.Variable;
import xbird.xquery.func.doc.Doc;
import xbird.xquery.func.doc.DocAvailable;
import xbird.xquery.func.doc.FnCollection;
import xbird.xquery.meta.DynamicContext;
import xbird.xquery.meta.XQueryContext;
import xbird.xquery.misc.QNameTable.QualifiedName;
import xbird.xquery.parser.visitor.AbstractXQueryParserVisitor;
/**
*
* <DIV lang="en"></DIV>
* <DIV lang="ja"></DIV>
*
* @author Makoto YUI (yuin405 AT gmail.com)
*/
public class QueryProcessor extends BackendProcessor {
private static final Log LOG = LogFactory.getLog(QueryProcessor.class);
protected final Semaphore _throttle;
protected final Map<RequestContext, Thread> _runningThreads = new ConcurrentIdentityHashMap<RequestContext, Thread>(12);
protected final ExecutorService _executors = ExecutorFactory.newCachedThreadPool(60L, "ParallelQP");
public QueryProcessor(ResponseListener handler) {
super(handler);
this._throttle = new Semaphore(ThrottedRemoteSequenceProxy.NUM_THROTTLE);
}
public Signature associatedWith() {
return Signature.QUERY;
}
public void fire(RequestContext rc) throws RemoteException {
rc.setFired(System.currentTimeMillis());
final Request request = rc.getRequest();
final Signature rsig = request.getSignature();
if(rsig != Signature.QUERY) {
throw new IllegalStateException("Illegal command is passed to QueryProcessor: " + rsig);
}
final QueryRequest queryRequest = (QueryRequest) request;
final String query = queryRequest.getQuery();
if(query == null) {
throw new IllegalStateException("query was null for: " + queryRequest);
}
// load shipped variables
final XQueryModule module = new XQueryModule();
final ShippedVariable[] vars = queryRequest.getShippedVariables();
if(vars != null) {
for(ShippedVariable var : vars) {
try {
module.putVariable(var.getVarName(), var);
} catch (XQueryException e) {
throw new RemoteException("failed to declare shipped variable: " + var, e);
}
}
}
final XQueryProcessor proccessor = new XQueryProcessor(module);
try {
proccessor.parse(query, queryRequest.getBaseUri());
} catch (XQueryException e) {
LOG.error("parse failed: \n" + query, e);
rc.setFault(e);
_resHandler.onResponse(rc);
return;
}
final Sequence<? extends Item> resultSeq;
_runningThreads.put(rc, Thread.currentThread());
final DynamicContext dynEnv = new DynamicContext(proccessor.getStaticContext());
try {
resultSeq = proccessor.execute(module, dynEnv);
} catch (Exception e) {
LOG.error("execute failed: \n" + query, e);
rc.setFault(e);
_resHandler.onResponse(rc);
return;
} finally {
_runningThreads.remove(rc);
}
final Serializable result = wrapResult(resultSeq, module.getExpression(), queryRequest, dynEnv);
rc.setResult(result);
try {
_resHandler.onResponse(rc);
} catch (RemoteException re) {
LOG.error("The respond for the request '" + rc.getRequest().getIdentifier()
+ "' is failed", re);
}
}
public void cancel(RequestContext rc) {
final Thread thread = _runningThreads.remove(rc);
if(thread != null) {
thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
_executors.shutdown();
}
protected final Serializable wrapResult(final Sequence result, final XQExpression queryExpr, final QueryRequest request, final DynamicContext dynEnv) {
assert (dynEnv != null);
final ReturnType returnType = request.getReturnType();
final Serializable ret;
switch(returnType) {
case SEQUENCE:
final List<Item> materialized = result.materialize();
ret = new ValueSequence(materialized, dynEnv);
break;
case MARSHALLED_SEQUENCE:
ret = new MarshalledSequence(result, dynEnv);
break;
case AUTO:
case REDIRECTABLE_MARSHALLED_SEQUENCE:
case REMOTE_PADED_SEQUENCE: {
final RedirectableChecker check = new RedirectableChecker();
try {
check.visit(queryExpr, null);
} catch (XQueryException e) {
throw new IllegalStateException(e);
}
if(check.isRedirectable()) {
MarshalledSequence seq = new MarshalledSequence(result, dynEnv);
seq.setRedirectable(true);
ret = seq;
} else if(returnType == ReturnType.REMOTE_PADED_SEQUENCE) {
MarshalledSequence seq = new MarshalledSequence(result, dynEnv);
seq.setRemotePaging(true);
ret = seq;
} else if(returnType == ReturnType.AUTO) {// treat as THROTTLED_ASYNC_REMOTE_SEQUENCE
final ThrottedRemoteSequenceProxy remote = new ThrottedRemoteSequenceProxy(_throttle, result, request);
try {
UnicastRemoteObject.exportObject(remote, 0, TimeoutSocketProdiver.createClientSocketFactory(), TimeoutSocketProdiver.createServerSocketFactory());
} catch (RemoteException e) {
throw new IllegalStateException("failed exporting result sequence", e);
}
ret = new RemoteSequence(remote, result.getType());
_executors.execute(remote);
} else {
assert (returnType == ReturnType.REDIRECTABLE_MARSHALLED_SEQUENCE) : returnType;
ret = new MarshalledSequence(result, dynEnv);
}
break;
}
case REMOTE_SEQUENCE: {
final RemoteSequenceProxy remote = new RemoteSequenceProxy(result, request);
try {
UnicastRemoteObject.exportObject(remote, 0, TimeoutSocketProdiver.createClientSocketFactory(), TimeoutSocketProdiver.createServerSocketFactory());
} catch (RemoteException e) {
throw new IllegalStateException("failed exporting result sequence", e);
}
ret = new RemoteSequence(remote, result.getType());
break;
}
case ASYNC_REMOTE_SEQUENCE: {
final RunnableRemoteSequenceProxy remote = new RunnableRemoteSequenceProxy(result, request);
try {
UnicastRemoteObject.exportObject(remote, 0, TimeoutSocketProdiver.createClientSocketFactory(), TimeoutSocketProdiver.createServerSocketFactory());
} catch (RemoteException e) {
throw new IllegalStateException("failed exporting result sequence", e);
}
ret = new RemoteSequence(remote, result.getType());
_executors.execute(remote);
break;
}
case THROTTLED_ASYNC_REMOTE_SEQUENCE: {
final ThrottedRemoteSequenceProxy remote = new ThrottedRemoteSequenceProxy(_throttle, result, request);
try {
UnicastRemoteObject.exportObject(remote, 0, TimeoutSocketProdiver.createClientSocketFactory(), TimeoutSocketProdiver.createServerSocketFactory());
} catch (RemoteException e) {
throw new IllegalStateException("failed exporting result sequence", e);
}
ret = new RemoteSequence(remote, result.getType());
_executors.execute(remote);
break;
}
case STRING:
ret = result.toString();
break;
default:
throw new IllegalStateException("Unexpected return type: " + returnType);
}
return ret;
}
private static final class RedirectableChecker extends AbstractXQueryParserVisitor {
private boolean _redirectable = false;
public RedirectableChecker() {
super();
}
public boolean isRedirectable() {
return _redirectable;
}
@Override
public XQExpression visit(DirectFunctionCall call, XQueryContext ctxt)
throws XQueryException {
if(!_redirectable) {
final QualifiedName funcName = call.getFuncName();
assert (funcName != null);
if(Doc.FUNC_NAME.equals(funcName) || DocAvailable.FUNC_NAME.equals(funcName)
|| FnCollection.FUNC_NAME.equals(funcName)) {
this._redirectable = false;
} else {
return super.visit(call, ctxt);
}
}
return call;
}
@Override
public XQExpression visit(BDQExpr expr, XQueryContext ctxt) throws XQueryException {
if(!_redirectable) {
this._redirectable = true;
}
return expr;
}
@Override
public XQExpression visit(Variable variable, XQueryContext ctxt) throws XQueryException {
XQExpression expr = variable.getValue();
if(expr != null) {
expr.visit(this, ctxt);
}
return variable;
}
}
}