package de.linwave.gtm;
import java.util.AbstractQueue;
import java.util.Comparator;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;
import org.odbms.FUNCTION;
import org.odbms.OP;
import org.odbms.ObjectContainer;
import org.odbms.Predicate;
import de.linwave.gtm.query.FUNCTIONImpl;
import de.linwave.gtm.query.OPImpl;
import de.linwave.gtm.query.Range;
import de.linwave.gtm.sort.SORT;
public class Constraint extends Thread
{
private static Logger logger = Logger.getLogger(Constraint.class.getName());
private static GTM gtm = GTM.getGTMInstance();
private static ObjectContainer session = GTM.getInstance();
private static final int MAX_POLL_CYCLES = 500;
AbstractQueue<Long> queueIN;
AbstractQueue<Long> queueOUT = new LinkedBlockingQueue<Long>();
// AbstractQueue<Long> queueOUT = new ArrayBlockingQueue<Long>(QueryImpl.MAX_QUEUE_SIZE);
// AbstractQueue<Long> queueOUT = new PriorityBlockingQueue<Long>();
String field;
OP operator;
SORT sort;
boolean lexicalSort;
Predicate predicate;
private Comparator<?> comparator;
private Class<?> clasz;
private FUNCTION function;
private Object value;
private Range range;
private boolean bypass;
private boolean completed;
private int polled = 0;
private int offered = 0;
private boolean notified;
/**
*
* @param clasz
*/
protected Constraint(Class<?> clasz) {
this.clasz = clasz;
}
/**
* A 'field' constrain implies 'sort by field'
*
* @param queueIN
* @param clasz
* @param field
*/
public Constraint(AbstractQueue<Long> queueIN, Class<?> clasz, String field, SORT sort, boolean lexicalSort) {
this.queueIN = queueIN;
this.clasz = clasz;
this.field = field;
this.sort = sort;
this.lexicalSort = lexicalSort;
}
/**
*
* @param queueIN
* @param clasz
* @param field
* @param operator
* @param function
* @param value
*/
public Constraint(AbstractQueue<Long> queueIN, Class<?> clasz, String field, OP operator, FUNCTION function, Object... objs) {
this.queueIN = queueIN;
this.clasz = clasz;
this.field = field;
this.operator = operator;
this.function = function;
if (operator == OP.RANGE) {
int cnt = 0;
Object from = null;
Object to = null;
for (Object obj : objs) {
if (cnt == 0)
from = obj;
else if (cnt == 1)
to = obj;
cnt++;
}
range = new Range(from, to);
} else {
for (Object obj : objs) {
if (obj instanceof Date) {
obj = ((Date) obj).getTime();
}
this.value = (function == null) ? obj : FUNCTIONImpl.eval(function, (String) obj);
break;
}
}
}
/**
*
* @param queueIN
* @param predicate
*/
public Constraint(AbstractQueue<Long> queueIN, Class<?> clasz, Predicate<?> predicate) {
this(queueIN, clasz, predicate, null);
}
/**
*
* @param queueIN
* @param predicate
* @param comparator
*/
public Constraint(AbstractQueue<Long> queueIN, Class<?> clasz, Predicate<?> predicate, Comparator<?> comparator) {
this.queueIN = queueIN;
this.predicate = predicate;
this.clasz = clasz;
this.comparator = comparator;
}
/**
* Start the thread Will be called from POQL.execute()
*/
public void execute()
{
this.start();
}
/**
*
*/
public void run()
{
if (queueIN != null) {
processINQueue();
} else {
boolean isIndexed = IndexUtils.isIndexed(clasz, field);
if (isIndexed) {
if (range != null)
processIndexWithRange();
else
processIndex();
} else {
fullTableScan();
}
}
}
/**
*
*/
private void processIndex()
{
long oid;
String key;
String gln;
bypass = true; // Do not evaluate again
logger.info("Process index class=" + clasz.getName() + " field=" + field + " value=" + value);
// Index name
String classGlobalNameIDX = GlobalName.Class2GlobalName(clasz) + "I";
String normalizedValue = IndexUtils.normalizeIndexKey(value);
if (operator == OP.EQUALS) { // Index scan with exact starting value
gln = GlobalName.buildGlobalName(classGlobalNameIDX, field, normalizedValue);
String oidKey = GlobalName.buildGlobalName(classGlobalNameIDX, field, normalizedValue, "");
while ((oidKey = gtm.ORDER(oidKey)) != null) {
oid = Long.parseLong(oidKey);
offer(oid);
oidKey = GlobalName.buildGlobalName(classGlobalNameIDX, field, normalizedValue, oid);
}
markEndOfQueue();
return;
} else if (operator == OP.STARTS_WITH || operator == OP.GREATER) { // Index scan with given starting value
gln = GlobalName.buildGlobalName(classGlobalNameIDX, field, normalizedValue);
} else if (operator == OP.CONTAINS || operator == OP.ENDS_WITH || operator == OP.SMALLER) { // Full index scan
gln = GlobalName.buildGlobalName(classGlobalNameIDX, field, -1);
} else {
throw new RuntimeException("Invalid Operation. Supported is only STARTS_WITH, ENDS_WITH, CONTAINS, EQUALS, GREATER, SMALLER on index");
}
int count = 0;
try {
while ((key = gtm.ORDER(gln)) != null) {
// No more items?
if (key == null)
break;
if (operator == OP.STARTS_WITH && !key.startsWith(normalizedValue))
break;
count++;
boolean addItem;
if (value instanceof String)
addItem = OPImpl.eval(operator, key, normalizedValue);
else
addItem = OPImpl.eval(operator, key, value);
gln = GlobalName.buildGlobalName(classGlobalNameIDX, field, key);
if (addItem) {
String oidKey = GlobalName.buildGlobalName(classGlobalNameIDX, field, key, -1);
while ((oidKey = gtm.ORDER(oidKey)) != null) {
oid = Long.parseLong(oidKey);
offer(oid);
oidKey = GlobalName.buildGlobalName(classGlobalNameIDX, field, key, oid);
}
}
}
} catch (Exception ex) {
throw new RuntimeException("Could not run query for clasz " + clasz, ex);
}
logger.info("Checked " + count + " index nodes (offered " + offered + " OID's) for index " + clasz.getName() + "(" + field + ")");
markEndOfQueue();
return;
}
/**
*
*/
private void processIndexWithRange()
{
long oid;
String key;
String gln;
bypass = true; // Do not evaluate again
logger.info("Process indexWithRange class=" + clasz.getName() + " field=" + field + " range=" + range);
// Index name
String classGlobalNameIDX = GlobalName.Class2GlobalName(clasz) + "I";
gln = GlobalName.buildGlobalName(classGlobalNameIDX, field, IndexUtils.normalizeIndexKey(range.getFrom()));
int count = 0;
try {
while ((key = gtm.ORDER(gln)) != null) {
// No more items?
if (key == null)
break;
boolean addItem = OPImpl.eval(range, key);
if (!addItem)
break;
count++;
gln = GlobalName.buildGlobalName(classGlobalNameIDX, field, key);
String oidKey = GlobalName.buildGlobalName(classGlobalNameIDX, field, key, -1);
while ((oidKey = gtm.ORDER(oidKey)) != null) {
oid = Long.parseLong(oidKey);
offer(oid);
oidKey = GlobalName.buildGlobalName(classGlobalNameIDX, field, key, oid);
}
}
} catch (Exception ex) {
throw new RuntimeException("Could not run query for clasz " + clasz, ex);
}
logger.info("Checked " + count + " index nodes (offered " + offered + " OID's) for index " + clasz.getName() + "(" + field + ")");
markEndOfQueue();
return;
}
private void processINQueue()
{
logger.info("process INQueue");
int cycles = MAX_POLL_CYCLES;
while (true) {
Long oid = queueIN.poll();
if (oid != null) {
if (oid.equals(GTM.END_OF_QUEUE)) {
break;
} else {
polled++;
offer(oid);
cycles = MAX_POLL_CYCLES;
}
} else {
cycles--;
if (cycles <= 0)
throw new RuntimeException("Could not receive objects from queue. Timeout!");
GTMUtils.sleep(25);
}
}
markEndOfQueue();
}
/**
*
*/
private void fullTableScan()
{
logger.info("fullTableScan");
long oid = -1;
try {
while (oid != GTM.END_OF_QUEUE) {
// Get the ID level
String key = GlobalName.buildGlobalName(clasz, oid);
String order = gtm.ORDER(key);
// No more items?
if (order == null) {
break;
} else {
// Add to queue
oid = Long.parseLong(order);
offer(oid);
}
}
} catch (Exception ex) {
throw new RuntimeException("Could not run query for clasz " + clasz, ex);
}
// Mark end of queue
markEndOfQueue();
}
/**
*
* @param oid
*/
private void offer(Long oid)
{
if (evaluate(oid)) {
while (!queueOUT.offer(oid))
GTMUtils.sleep(10);
offered++;
if (!notified)
notifyQueue();
}
}
/**
*
* @param oid
* @return
*/
protected boolean evaluate(long oid)
{
if (predicate != null) {
return predicate.match(session.getByID(clasz, oid));
} else {
if (bypass || operator == null)
return true;
String globalName = GlobalName.buildGlobalName(clasz, oid, field);
String globalValue = gtm.GET(globalName);
if (globalValue == null)
return false;
if (function != null)
globalValue = FUNCTIONImpl.eval(function, globalValue);
if (operator == OP.RANGE) {
return OPImpl.eval(range, globalValue);
} else {
return OPImpl.eval(operator, globalValue, value);
}
}
}
/**
*
*/
protected void notifyQueue()
{
if (!notified) {
synchronized (queueOUT) {
queueOUT.notify();
}
notified = true;
}
}
/**
*
*/
private void markEndOfQueue()
{
queueOUT.offer(GTM.END_OF_QUEUE);
completed = true;
}
public void sort()
{
this.sort = SORT.ASCENDING;
}
public void sortDescending()
{
this.sort = SORT.DESCENDING;
}
public Comparator getComparator()
{
return comparator;
}
public Class<?> getClasz()
{
return clasz;
}
public AbstractQueue<Long> getQueueOUT()
{
return queueOUT;
}
public void setQueueOUT(AbstractQueue<Long> queueOUT)
{
this.queueOUT = queueOUT;
}
/**
*
* @return
*/
public boolean isCompleted()
{
return completed;
}
public void waitForCompleted()
{
while (!completed) {
GTMUtils.sleep(25);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("class=" + clasz.getName());
if (field != null) {
sb.append(", field=" + field);
sb.append(", op=" + operator);
sb.append(", func=" + function);
sb.append(", value=" + value);
}
if (predicate != null) {
sb.append(", predicate=" + predicate);
}
sb.append(", bypass=" + bypass);
sb.append("; polled=" + polled);
sb.append(", offered=" + offered);
sb.append(", notified=" + notified);
sb.append(", completed=" + completed);
return sb.toString();
}
}