package com.bagri.xdm.client.jcache.impl;
import static com.bagri.xdm.client.common.XDMCacheConstants.CN_XDM_RESULT;
import static com.bagri.xdm.client.common.XDMCacheConstants.CN_XDM_QUERY;
import static com.bagri.xdm.client.common.XDMCacheConstants.PN_XDM_SCHEMA_POOL;
import static com.bagri.xdm.common.XDMConstants.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.spi.CachingProvider;
import javax.xml.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bagri.xdm.api.XDMException;
import com.bagri.xdm.api.XDMQueryManagement;
import com.bagri.xdm.client.common.impl.QueryManagementBase;
import com.bagri.xdm.client.hazelcast.impl.RepositoryImpl;
import com.bagri.xdm.client.hazelcast.impl.ResultCursor;
import com.bagri.xdm.client.hazelcast.task.query.DocumentIdsProvider;
import com.bagri.xdm.client.hazelcast.task.query.QueryExecutor;
import com.bagri.xdm.domain.XDMQuery;
import com.bagri.xdm.domain.XDMResults;
import com.hazelcast.cache.HazelcastCachingProvider;
import com.hazelcast.cache.ICache;
import com.hazelcast.core.IExecutorService;
import com.hazelcast.core.IMap;
import com.hazelcast.core.Member;
import com.hazelcast.core.ReplicatedMap;
public class QueryManagementImpl extends QueryManagementBase implements XDMQueryManagement {
private final static Logger logger = LoggerFactory.getLogger(QueryManagementImpl.class);
private boolean queryCache = true;
private RepositoryImpl repo;
private IExecutorService execService;
private Future execution = null;
//private IMap<Long, XDMResults> resCache;
private Cache<Long, XDMResults> resCache;
private ReplicatedMap<Integer, XDMQuery> xqCache;
public QueryManagementImpl() {
// what should we do here?
}
void initialize(RepositoryImpl repo) {
this.repo = repo;
execService = repo.getHazelcastClient().getExecutorService(PN_XDM_SCHEMA_POOL);
//resCache = repo.getHazelcastClient().getMap(CN_XDM_RESULT);
xqCache = repo.getHazelcastClient().getReplicatedMap(CN_XDM_QUERY);
CachingProvider cachingProvider = Caching.getCachingProvider();
// retrieve the javax.cache.CacheManager
try {
URI uri = new URI("hzClient");
Properties props = HazelcastCachingProvider.propertiesByInstanceName(repo.getHazelcastClient().getName());
CacheManager cacheManager = cachingProvider.getCacheManager(uri, null, props);
resCache = cacheManager.getCache(CN_XDM_RESULT, Long.class, XDMResults.class);
} catch (URISyntaxException ex) {
logger.error("initialize.error;", ex);
}
}
public void setQueryCache(boolean queryCache) {
this.queryCache = queryCache;
}
@Override
public void cancelExecution() throws XDMException {
if (execution != null) {
// synchronize on it?
if (!execution.isDone()) {
execution.cancel(true);
}
}
}
@Override
public Collection<String> getDocumentUris(String query, Map<QName, Object> params, Properties props) throws XDMException {
long stamp = System.currentTimeMillis();
logger.trace("getDocumentUris.enter; query: {}", query);
DocumentUrisProvider task = new DocumentUrisProvider(repo.getClientId(), repo.getTransactionId(), query, params, props);
Future<Collection<XDMDocumentId>> future = execService.submit(task);
execution = future;
Collection<String> result = getResults(future, 0);
stamp = System.currentTimeMillis() - stamp;
logger.trace("getDocumentUris.exit; time taken: {}; returning: {}", stamp, result);
return result;
}
@Override
public Iterator executeQuery(String query, Map<QName, Object> params, Properties props) throws XDMException {
logger.trace("executeQuery.enter; query: {}; bindings: {}; context: {}", query, params, props);
boolean useCache = this.queryCache;
String qCache = props.getProperty(pn_client_queryCache);
if (qCache != null) {
useCache = Boolean.parseBoolean(qCache);
}
if (useCache) {
long key = getResultsKey(query, params);
XDMResults res = resCache.get(key);
if (res != null) {
logger.trace("execXQuery; got cached results: {}", res);
return res.getResults().iterator();
}
}
props.setProperty(pn_client_id, repo.getClientId());
//props.setProperty(pn_client_txId, String.valueOf(repo.getTransactionId()));
long key = getQueryKey(query);
boolean isQuery = true;
QueryExecutor task = new QueryExecutor(repo.getClientId(), repo.getTransactionId(), query, params, props);
Future<ResultCursor> future;
String runOn = props.getProperty(pn_client_submitTo, pv_client_submitTo_any);
if (pv_client_submitTo_owner.equalsIgnoreCase(runOn)) {
future = execService.submitToKeyOwner(task, key);
} else if (pv_client_submitTo_member.equalsIgnoreCase(runOn)) {
Member member = repo.getHazelcastClient().getPartitionService().getPartition(key).getOwner();
future = execService.submitToMember(task, member);
} else {
future = execService.submit(task);
}
execution = future;
long timeout = Long.parseLong(props.getProperty(pn_queryTimeout, "0"));
//if (cursor != null) {
// cursor.close(false);
//}
ResultCursor cursor = getResults(future, timeout);
logger.trace("execXQuery; got cursor: {}", cursor);
if (cursor != null) {
cursor.deserialize(repo.getHazelcastClient());
}
Iterator result;
int fetchSize = Integer.parseInt(props.getProperty(pn_client_fetchSize, "0"));
if (fetchSize == 0) {
result = extractFromCursor(cursor);
} else {
// possible memory leak with non-closed cursors !?
result = cursor;
}
logger.trace("executeQuery.exit; returning: {}", result);
return result;
}
private <T> T getResults(Future<T> future, long timeout) throws XDMException {
T result;
try {
if (timeout > 0) {
result = future.get(timeout, TimeUnit.MILLISECONDS); // SECONDS);
} else {
result = future.get();
}
return result;
} catch (TimeoutException ex) {
logger.warn("getResults.timeout; request timed out after {}; cancelled: {}", timeout, future.isCancelled());
future.cancel(true);
throw new XDMException(ex, XDMException.ecQueryTimeout);
} catch (InterruptedException | ExecutionException ex) {
int errorCode = XDMException.ecQuery;
if (ex.getCause() != null && ex.getCause() instanceof CancellationException) {
errorCode = XDMException.ecQueryCancel;
logger.warn("getResults.interrupted; request cancelled: {}", future.isCancelled());
} else {
future.cancel(false);
logger.error("getResults.error; error getting result", ex);
Throwable err = ex;
while (err.getCause() != null) {
err = err.getCause();
if (err instanceof XDMException) {
throw (XDMException) err;
}
}
}
throw new XDMException(ex, errorCode);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Iterator extractFromCursor(ResultCursor cursor) {
List result = new ArrayList(cursor.getQueueSize());
while (cursor.hasNext()) {
result.add(cursor.next());
}
return result.iterator();
}
@Override
public Collection<String> prepareQuery(String query) { //throws XDMException {
logger.trace("prepareQuery.enter; query: {}", query);
Collection<String> result = null;
XDMQuery xq = xqCache.get(getQueryKey(query));
if (xq != null) {
result = xq.getXdmQuery().getParamNames();
}
logger.trace("prepareQuery.exit; returning: {}", query);
return result;
}
}