package com.yahoo.dtf.query;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import com.yahoo.dtf.query.QueryIntf;
import com.yahoo.dtf.actions.Action;
import com.yahoo.dtf.actions.conditionals.And;
import com.yahoo.dtf.actions.conditionals.Condition;
import com.yahoo.dtf.actions.conditionals.Eq;
import com.yahoo.dtf.actions.conditionals.False;
import com.yahoo.dtf.actions.conditionals.Gt;
import com.yahoo.dtf.actions.conditionals.Lt;
import com.yahoo.dtf.actions.conditionals.Neq;
import com.yahoo.dtf.actions.conditionals.Or;
import com.yahoo.dtf.actions.conditionals.True;
import com.yahoo.dtf.actions.event.Field;
import com.yahoo.dtf.actions.properties.Match;
import com.yahoo.dtf.exception.DTFException;
import com.yahoo.dtf.exception.ParseException;
import com.yahoo.dtf.exception.QueryException;
import com.yahoo.dtf.exception.StorageException;
public class TxtQuery extends QueryIntf {
private BufferedReader _br = null;
private URI _uri = null;
private String _event = null;
private String _property = null;
private String _encoding = null;
private Condition _constraints = null;
private ArrayList _fields = null;
private ArrayList _fieldNames = null;
private long _resetcount = 0;
public synchronized void close() throws QueryException {
try {
_br.close();
} catch (IOException e) {
throw new QueryException("Error closing filehandle.",e);
}
}
public HashMap next(boolean recycle) throws QueryException {
return readNext(recycle);
}
private Condition process(Condition cond,
LinkedHashMap<String, String> props,
String eventName)
throws QueryException, ParseException {
Condition conditional = null;
if (cond instanceof And) {
conditional = new And();
ArrayList<Action> children = cond.children();
for (int i = 0; i < cond.children().size(); i++) {
conditional.addAction(process((Condition)children.get(i),props,eventName));
}
} else if (cond instanceof Or) {
conditional = new Or();
ArrayList children = cond.children();
for (int i = 0; i < cond.children().size(); i++) {
conditional.addAction(process((Condition)children.get(i),props,eventName));
}
} else {
String field = null;
String value = null;
if (props.containsKey(eventName + "." + cond.getOp1())) {
field = cond.getOp1();
value = cond.getOp2();
} else if (props.containsKey(eventName + "." + cond.getOp2())) {
field = cond.getOp2();
value = cond.getOp1();
} else {
if (cond.getNullable()) {
/*
* None of the operators exist therefore if this is a nullable
* element then we can just have always true condition because
* the field is null logically :)
*/
return new True();
} else
return new False();
}
String fValue = props.get(eventName + "." + field);
if (fValue == null)
throw new QueryException("Constraint field [" + field +
"] does not exist in results.");
if (cond instanceof Eq) {
conditional = new Eq();
} else if (cond instanceof Neq) {
conditional = new Neq();
} else if (cond instanceof Lt) {
conditional = new Lt();
} else if (cond instanceof Gt) {
conditional = new Gt();
} else if (cond instanceof Match) {
conditional = new Match();
}
if (conditional == null)
throw new QueryException("Uknown conditional: " +
cond.getClass());
conditional.setOp1(fValue);
conditional.setOp2(value);
}
return conditional;
}
private synchronized HashMap readNext(boolean recycle) throws QueryException {
try {
String line = _br.readLine();
long linecount = 0;
while (line != null) {
String eventName = null;
/*
* Read a block of properties since we know that TXTRecorder
* separates events by an empty line with a new line.
*/
LinkedHashMap<String, String> props =
new LinkedHashMap<String, String>();
while ((line != null) && (!line.trim().equals(""))) {
linecount++;
// comment skip this line
if (line.charAt(0) != '#') {
// using split is more expensive because it uses Pattern
// class to do the matching.
int index = line.indexOf('=');
if (index == -1) {
throw new QueryException("Event information has an "
+ "invalid encoding for "
+ "contents [" + line
+ "] at line " + linecount);
}
/*
* XXX: may not be the best way of doing this but for
* now if you want to create your own txt recorder in
* another language and then process this with DTF
* you'll have to make sure start is the first attribute
* for each event in the event files.
*/
String key = line.substring(0,index);
if ( eventName == null ) {
if (key.endsWith(".start")) {
eventName = key.substring(0, key.lastIndexOf("."));
} else {
throw new QueryException("All events in txt query file shoud begin with the xxx.start attribute [" + line + "]");
}
}
String keyString = key.substring(eventName.length() + 1);
if (_fieldNames == null || _fieldNames.contains(keyString)) {
String value = line.substring(index+1);
value = URLDecoder.decode(value,_encoding);
props.put(key, value);
}
}
line = _br.readLine();
}
/*
* Skip events that are not of the type specified. The easiest
* way to do this is to look for the default start property that
* would exist for every event recorded with the DTF event
* recording framework.
*/
if (_event != null) {
if (props.get(_event + ".stop") == null) {
line = _br.readLine();
linecount++;
continue;
}
}
if (_constraints != null) {
Condition cond = process(_constraints, props, eventName);
if ( !cond.evaluate() ) {
line = _br.readLine();
linecount++;
continue;
}
}
/*
* If we get here without having failed to meet the contraints
* then the current event is the one we should return.
*/
HashMap<String, String> result = new LinkedHashMap<String, String>();
Iterator<Entry<String,String>> entries =
props.entrySet().iterator();
String prop = _property + ".";
while ( entries.hasNext() ) {
Entry<String,String> entry = entries.next();
String key = entry.getKey();
String value = entry.getValue();
/*
* All results fields are store in the result attribute
*/
String keyString = key.substring(eventName.length() + 1);
if (_property != null) {
result.put(prop + keyString, value);
} else {
result.put(key, value);
}
}
if ( _property != null )
result.put(_property + ".event.name", eventName);
else
result.put("event.name", eventName);
return result;
}
if (recycle) {
reset();
return next(false);
}
/*
* If we got this far and have nothing to return then we failed to
* find an event that matches the requested query.
*/
return null;
} catch (IOException e) {
throw new QueryException("Error reading file.",e);
} catch (ParseException e) {
throw new QueryException("Parsing exception.",e);
} catch (DTFException e) {
throw new QueryException("Parsing exception.",e);
}
}
@Override
public synchronized void reset() throws QueryException {
try {
_br.close();
open(_uri, _fields, _constraints, _event, _property, _encoding);
_resetcount++;
} catch (IOException e) {
throw new QueryException("Error reading file.",e);
}
}
@Override
public synchronized long getResetCount() { return _resetcount; }
public String getProperty() { return _property; }
public synchronized void open(URI uri,
ArrayList fields,
Condition constraints,
String event,
String property,
String encoding) throws QueryException {
InputStream is = null;
try {
is = Action.getStorageFactory().getInputStream(uri,true);
} catch (StorageException e) {
throw new QueryException("Problem accessing storage.",e);
}
_uri = uri;
_constraints = constraints;
_fields = fields;
_event = event;
_property = property;
_encoding = encoding;
try {
InputStreamReader isr = new InputStreamReader(is,_encoding);
_br = new BufferedReader(isr);
} catch (UnsupportedEncodingException e) {
throw new QueryException("Bad encoding specified.", e);
}
if (_fields != null) {
_fieldNames = new ArrayList();
for(int i = 0 ; i < _fields.size(); i++) {
try {
_fieldNames.add(((Field)_fields.get(i)).getName());
} catch (ParseException e) {
throw new QueryException("Unable to get field name.",e);
}
}
if (!_fieldNames.contains("start"))
_fieldNames.add("start");
if (!_fieldNames.contains("stop"))
_fieldNames.add("stop");
if (!_fieldNames.contains("event.name"))
_fieldNames.add("event.name");
}
}
}