just as an update: the above implementation was clearly a brainstorm, and the devil has been in the details. i now have this implemented and working almost exactly as i want it to, and it is quite flexible. i've added a "set" concept that wraps the conjunction/disjunction to allow for complex and/or clauses. the only problem i have now is the problem with criteria and inner joins.
evidently every time you add an alias, it INNER JOIN's the alias to the query. so you can lose rows if your not thinking about it.
for example:
i have a set of applicants to a school.
each applicant has an address, a set of tests with scores, and a set of experiences (like previous work).
i want to build a criteria that says
"get me all the applicants who either
1) live in houston
2) have test scores > 95%
3) have previous experience"
this will skip anyone who does not have any experiences (since it tries to inner join to experiences, and then i lose all those rows). even if those people happen to meet one of the other two criteria. in SQL, this is easly to solve. actually, its easy in HQL as well. however, building a web-GUI for end-users to define the above query via HQL would be a big pain. It seems to me that this is the whole point of the criteria API.
there have been a couple other threads about this, even an unrelated thread in JiRA that mentions this as an aside, but i've seen no concrete solutions. anyone have any ideas?
by the way, here is my "storedquery" code if anyone wants it.
Code:
public Criteria getCompiledCriteria(boolean keepSessionOpen) throws RAMException {
Session session = HibernateSessionFactory.currentSession();
try {
Class queryClass = Class.forName(rootClass);
Criteria masterCriteria = session.createCriteria(queryClass);
aliases.clear();
Junction currentJunction = null;
StoredQueryStep currentStep = firstStep;
Stack junctions = new Stack();
while (currentStep != null) {
switch (currentStep.getCommand()) {
case StoredQueryStep.CLOSE_SET:
if (junctions.size() == 0) throw new RAMInvalidQueryException("you closed a set when there were no sets left to be closed");
Junction junctionToClose = (Junction) junctions.pop();
if (junctions.size() > 0) {
Junction nextJunction = (Junction) junctions.peek();
nextJunction.add(junctionToClose);
} else {
masterCriteria.add(junctionToClose);
}
break;
case StoredQueryStep.OPEN_SET_WITH_AND:
Junction newAndJunction = Expression.conjunction();
junctions.push(newAndJunction);
break;
case StoredQueryStep.OPEN_SET_WITH_OR:
Junction newOrJunction = Expression.disjunction();
junctions.push(newOrJunction);
break;
case StoredQueryStep.PROCESS_GROUP:
//cannot process without a set having been opened first
if (junctions.size()==0) throw new RAMInvalidQueryException("you tried to process a criteriagroup before opening a set");
currentJunction = (Junction)junctions.peek(); //don't take it off the stack
//we should have a valid currentJunction by now
if (currentJunction == null) throw new RAMInvalidQueryException("Invalid nesting in query. check your open and close set statements!");
CriteriaGroup currentGroup = currentStep.getCriteriaGroup();
if (currentGroup ==null) throw new RAMInvalidQueryException("you must specify a criteria group for each 'processing' step");
Junction innerJunction = (currentGroup.getCombinationType()==CriteriaGroup.AND) ?
(Junction) Expression.conjunction() : (Junction) Expression.disjunction();
for (Iterator i=currentGroup.getCriteria().iterator(); i.hasNext();) {
StoredCriteria currentStoredCriteria = (StoredCriteria) i.next();
createCriteria(masterCriteria, innerJunction, currentStoredCriteria);
}
currentJunction.add(innerJunction);
break;
}
currentStep = currentStep.getNextStep();
}
//close all the junctions that are left on the stack
//this in case people are lazy about closing sets
while (junctions.size() > 0) {
currentJunction=(Junction) junctions.pop();
if (junctions.size() > 0) {
Junction nextJunction = (Junction) junctions.peek();
nextJunction.add(currentJunction);
}
else
masterCriteria.add(currentJunction);
}
masterCriteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return masterCriteria;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RAMException("the root class supplied in the data is not valid!", e);
} finally {
HibernateSessionFactory.closeSession(keepSessionOpen);
}
}
private String getNextAliasName(String suffix) {
return "alias" + Integer.toString(aliasIndex++) + suffix;
}
private String createAlias(String path, Criteria criteria) throws HibernateException{
return _createAlias(path, criteria, "", path, 0);
}
private String getCurrentPath(String path, int index) {
if (path.indexOf(".")<0) return path;
String[] tokens = path.split("\\.");
String ret = "";
for (int i=0; i<=index; i++) {
if (ret.length() > 0) ret+= ".";
ret+=tokens[i];
}
return ret;
}
private String _createAlias (String path, Criteria criteria, String currentAlias, String originalPath, int currentIndex) throws HibernateException{
String aliasName = null;
//try to find the biggest matching alias?
String currentPath = getCurrentPath(originalPath, currentIndex);
if (path.indexOf(".") >=0 ) {
//then get the first token
//and send it as the parent
String[] tokens = path.split("\\.");
aliasName = (String) aliases.get(currentPath);
if (aliasName == null) {
String pathToHead;
//make an alias for the head
aliasName = getNextAliasName(tokens[0]);
if (currentAlias.length()>0)
pathToHead = currentAlias + "." + tokens[0];
else
pathToHead = tokens[0];
criteria.createAlias(pathToHead, aliasName);
criteria.setFetchMode(currentPath, FetchMode.EAGER);
aliases.put(currentPath, aliasName);
}
//the remainder of the string needs to be processed
String remainder ="";
for (int i=1; i<tokens.length; i++) {
if (remainder.length() > 0)
remainder += ".";
remainder+=tokens[i];
}
//recursive
return _createAlias(remainder, criteria, aliasName, originalPath, currentIndex+1);
}
else {
//we are at a "leaf"
aliasName = (String) aliases.get(currentPath);
if (aliasName==null) {
aliasName = getNextAliasName(path);
if (currentAlias.length() >0) currentAlias += ".";
criteria.createAlias(currentAlias + path, aliasName);
criteria.setFetchMode(currentPath, FetchMode.EAGER);
aliases.put(currentPath, aliasName);
}
}
return aliasName;
}
public Criteria createCriteria(Criteria masterCriteria, Junction currentJunction, StoredCriteria currentCriteria) throws RAMException{
try {
if (masterCriteria == null) return null;
if (currentCriteria == null) return masterCriteria;
String path = currentCriteria.getPath();
Criterion criterion = null;
if (path!=null) {
String aliasName = ((CriteriaImpl) masterCriteria).getAlias(path);
//String aliasName = (String) aliases.get(path);
if (aliasName==null) {
aliasName = createAlias(path, masterCriteria);
aliases.put(path, aliasName);
}
criterion = currentCriteria.generateCriterion(aliasName + "." + currentCriteria.getFieldName());
}
else {
criterion = currentCriteria.generateCriterion(currentCriteria.getFieldName());
}
if (criterion != null)
currentJunction.add(criterion);
return masterCriteria;
} catch (HibernateException e) {
e.printStackTrace();
throw new RAMException(e);
}
}
there are some more details, if you'd like to see all the code, just let me know (maulin AT houston DOT rr DOT com)