/**
*
*/
package com.bagri.server.hazelcast.management;
import static com.bagri.core.Constants.*;
import static com.bagri.support.util.PropUtils.getOutputProperties;
import static com.bagri.support.util.XQUtils.props2Context;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import javax.xml.namespace.QName;
import javax.xml.xquery.XQConnection;
import javax.xml.xquery.XQException;
import javax.xml.xquery.XQExpression;
import javax.xml.xquery.XQPreparedExpression;
import javax.xml.xquery.XQResultSequence;
import javax.xml.xquery.XQStaticContext;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;
import com.bagri.core.api.ResultCursor;
import com.bagri.core.api.BagriException;
import com.bagri.core.xquery.api.XQProcessor;
import com.bagri.server.hazelcast.task.schema.SchemaQueryCleaner;
import com.bagri.server.hazelcast.task.stats.StatisticSeriesCollector;
import com.bagri.server.hazelcast.task.stats.StatisticsReseter;
import com.bagri.support.stats.StatsAggregator;
import com.bagri.xqj.BagriXQConnection;
import com.hazelcast.core.Member;
/**
* @author Denis Sukhoroslov
*
*/
@ManagedResource(description="(X)Query Management MBean")
public class QueryManagement extends SchemaFeatureManagement {
private int fetchSize = 0;
private int queryTimeout = 0;
private XQConnection xqConn;
private StatsAggregator qcAggregator;
private com.bagri.core.api.QueryManagement queryMgr;
public QueryManagement(String schemaName) {
super(schemaName);
}
@Override
public void setSchemaManager(SchemaManager schemaManager) {
super.setSchemaManager(schemaManager);
queryMgr = schemaManager.getRepository().getQueryManagement();
}
public void setXQConnection(XQConnection xqConn) {
this.xqConn = xqConn;
}
@Override
protected String getFeatureKind() {
return "QueryManagement";
}
@ManagedOperation(description="clear Query cache")
public boolean clear() {
SchemaQueryCleaner task = new SchemaQueryCleaner(schemaName);
Map<Member, Future<Boolean>> results = execService.submitToAllMembers(task);
boolean result = true;
for (Map.Entry<Member, Future<Boolean>> entry: results.entrySet()) {
try {
if (!entry.getValue().get()) {
result = false;
}
} catch (InterruptedException | ExecutionException ex) {
logger.error("clear.error; ", ex);
}
}
return result;
}
@ManagedOperation(description="Cancel currently running query started from the same JMX connection")
public void cancelQuery() {
try {
// are we in exec state now?
XQProcessor xqp = ((BagriXQConnection) xqConn).getProcessor();
xqp.cancelExecution();
} catch (XQException ex) {
logger.error("cancelQuery.error", ex);
throw new RuntimeException(ex.getMessage());
}
}
@ManagedOperation(description="Parse XQuery. Return array of parameter names, if any")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "query", description = "A query request provided in XQuery syntax")})
public String[] parseQuery(String query, Properties props) {
XQPreparedExpression xqpExp = null;
try {
XQStaticContext ctx = xqConn.getStaticContext();
props2Context(schemaManager.getEntity().getProperties(), ctx);
props2Context(props, ctx);
xqpExp = xqConn.prepareExpression(query, ctx);
QName[] vars = xqpExp.getAllExternalVariables();
String[] result = null;
if (vars != null) {
result = new String[vars.length];
for (int i=0; i < vars.length; i++) {
result[i] = vars[i].toString();
}
}
xqpExp.close();
return result;
} catch (XQException ex) {
logger.error("parseQuery.error", ex);
throw new RuntimeException(ex.getMessage());
}
}
@ManagedOperation(description="Run XQuery. Returns string output specified by XQuery")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "query", description = "A query request provided in XQuery syntax"),
@ManagedOperationParameter(name = "useXDM", description = "use XDM (true) or XQJ query interface"),
@ManagedOperationParameter(name = "props", description = "Query processing properties")})
public String runQuery(String query, boolean useXDM, Properties props) {
String result = null;
try {
if (useXDM) {
ResultCursor cursor = queryMgr.executeQuery(query, null, props);
result = extractResult(cursor, props);
} else {
XQStaticContext ctx = xqConn.getStaticContext();
props2Context(schemaManager.getEntity().getProperties(), ctx);
props2Context(props, ctx);
XQExpression xqExp = xqConn.createExpression(ctx);
XQResultSequence xqSec = xqExp.executeQuery(query);
result = xqSec.getSequenceAsString(props);
xqSec.close();
xqExp.close();
return result;
}
return result;
} catch (XQException | BagriException ex) {
logger.error("runQuery.error", ex);
throw new RuntimeException(ex.getMessage());
}
}
private String extractResult(ResultCursor cursor, Properties props) throws XQException, BagriException {
StringBuffer buff = new StringBuffer();
//cursor.deserialize(((RepositoryImpl) schemaManager.getRepository()).getHzInstance());
XQProcessor xqp = ((BagriXQConnection) xqConn).getProcessor();
Properties outProps = getOutputProperties(props);
int fSize = Integer.parseInt(props.getProperty(pn_client_fetchSize, String.valueOf(fetchSize)));
if (fSize > 0) {
int cnt = 0;
while (cursor.next() && cnt < fSize) {
buff.append(xqp.convertToString(cursor.getXQItem(), outProps));
cnt++;
}
} else {
while (cursor.next()) {
buff.append(xqp.convertToString(cursor.getXQItem(), outProps));
}
}
return buff.toString();
}
@ManagedOperation(description="Run XQuery. Returns string output specified by XQuery")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "query", description = "A query request provided in XQuery syntax"),
@ManagedOperationParameter(name = "useXDM", description = "use XDM (true) or XQJ query interface"),
@ManagedOperationParameter(name = "bindings", description = "A map of query parameters"),
@ManagedOperationParameter(name = "props", description = "Query processing properties")})
public String runPreparedQuery(String query, boolean useXDM, CompositeData bindings, Properties props) {
logger.trace("runPreparedQuery.enter; got bindings: {}, properties: {}", bindings, props);
if (bindings == null) {
return runQuery(query, useXDM, props);
}
String result;
try {
if (useXDM) {
Set<String> keys = bindings.getCompositeType().keySet();
Map<String, Object> params = new HashMap<>(keys.size());
for (String key: keys) {
params.put(key, bindings.get(key));
}
ResultCursor cursor = queryMgr.executeQuery(query, params, props);
result = extractResult(cursor, props);
} else {
XQStaticContext ctx = xqConn.getStaticContext();
props2Context(schemaManager.getEntity().getProperties(), ctx);
props2Context(props, ctx);
XQPreparedExpression xqpExp = xqConn.prepareExpression(query, ctx);
for (String key: bindings.getCompositeType().keySet()) {
xqpExp.bindObject(new QName(key), bindings.get(key), null);
}
XQResultSequence xqSec = xqpExp.executeQuery();
result = xqSec.getSequenceAsString(props);
xqSec.close();
xqpExp.close();
}
} catch (XQException | BagriException ex) {
logger.error("runPreparedQuery.error", ex);
throw new RuntimeException(ex.getMessage());
}
logger.trace("runPreparedQuery.exit; returning: {}", result);
return result;
}
private void setQueryProperties(Properties props) {
props.setProperty(pn_client_fetchSize, String.valueOf(fetchSize));
props.setProperty(pn_xqj_queryTimeout, String.valueOf(queryTimeout));
}
@ManagedAttribute(description="Returns aggregated QueryManagement invocation statistics, per method")
public TabularData getInvocationStatistics() {
return super.getSeriesStatistics(new StatisticSeriesCollector(schemaName, "queryStats"), aggregator);
}
@ManagedOperation(description="Reset QueryManagement invocation statistics")
public void resetStatistics() {
super.resetStatistics(new StatisticsReseter(schemaName, "queryStats"));
}
@ManagedAttribute(description="Return aggregated query usage statistics, per cached query")
public TabularData getQueryCacheStatistics() {
if (qcAggregator == null) {
qcAggregator = new StatsAggregator() {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object[] aggregateStats(Object[] source, Object[] target) {
target[0] = (Integer) source[0] + (Integer) target[0]; // accessed
target[1] = (Integer) source[1] + (Integer) target[1]; // cached results
target[2] = ((Comparable) source[2]).compareTo((Comparable) target[2]) < 0 ? source[2] : target[2]; // first
target[3] = (Integer) source[3] + (Integer) target[3]; // hits
target[4] = ((Comparable) source[4]).compareTo((Comparable) target[4]) > 0 ? source[4] : target[4]; // last
target[5] = (Integer) source[5] + (Integer) target[5]; // miss
target[6] = source[6]; // query
target[7] = (Integer) source[7] + (Integer) target[7]; // result hits
target[8] = (Integer) source[8] + (Integer) target[8]; // result miss
return target;
}
};
}
return super.getUsageStatistics(new StatisticSeriesCollector(schemaName, "queryCacheStats"), qcAggregator);
}
@ManagedAttribute(description="Returns query fetch size limit in records. 0 means no limit")
public int getFetchSize() {
return fetchSize;
}
@ManagedAttribute(description="Set query fetch size limit in records. 0 means no limit")
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
@ManagedAttribute(description="Returns query timeoit in seconds. 0 means no timeout")
public int getQueryTimeout() {
return queryTimeout;
}
@ManagedAttribute(description="Set query timeout in seconds. 0 means no timeout")
public void setQueryTimeout(int queryTimeout) {
this.queryTimeout = queryTimeout;
}
}