001: package com.jofti.parser;
002:
003: import java.io.Reader;
004: import java.io.StringReader;
005: import java.util.Comparator;
006: import java.util.HashMap;
007: import java.util.Iterator;
008: import java.util.LinkedHashMap;
009: import java.util.List;
010: import java.util.Map;
011: import java.util.Stack;
012:
013: import org.apache.commons.logging.Log;
014: import org.apache.commons.logging.LogFactory;
015:
016: import antlr.collections.AST;
017:
018: import com.jofti.api.IndexQuery;
019: import com.jofti.core.IParsedQuery;
020: import com.jofti.core.IPredicate;
021: import com.jofti.exception.JoftiException;
022: import com.jofti.introspect.ClassIntrospector;
023: import com.jofti.parser.ejb.EJB3QueryParser;
024: import com.jofti.query.EJBQuery;
025: import com.jofti.util.ArrayComparator;
026: import com.jofti.util.CompositeComparator;
027: import com.jofti.util.ReflectionComparator;
028: import com.jofti.util.ReflectionUtil;
029:
030: /**
031: * This class does the work in turning a test EJBQuery into an IParsedQuery, which is the
032: * internal query representation.</p>
033: *
034: *
035: * @author xenephon
036: * @version 1.0
037: */
038: public class EJBQueryParser extends AbstractParser implements
039: IQueryParser {
040:
041: // constructs a parser with a set number of cache queries
042: public EJBQueryParser(final int cachedQueries,
043: ClassIntrospector introspector) {
044: super (cachedQueries, introspector);
045: }
046:
047: // default constructor does 100 queries
048: public EJBQueryParser(ClassIntrospector introspector) {
049: this (100, introspector);
050: }
051:
052: private static Log log = LogFactory.getLog(ParserManager.class);
053:
054: /* (non-Javadoc)
055: * @see com.jofti.parser.IQueryParser#parseQuery(com.jofti.api.IndexQuery)
056: */
057: public IParsedQuery parseQuery(IndexQuery originalQuery)
058: throws JoftiException {
059:
060: return parseQuery(((EJBQuery) originalQuery).getQuery(),
061: originalQuery);
062:
063: }
064:
065: public IParsedQuery getQuery(String name) {
066:
067: ParsedQuery query = null;
068: synchronized (lockObject) {
069: query = (ParsedQuery) compiledQueries.get(name);
070: }
071: if (query != null) {
072: ParsedQueryWrapper wrapper = new ParsedQueryWrapper(query);
073:
074: // add in the name parameters if we have any
075: wrapper.setNamedValueMap(new HashMap());
076:
077: return wrapper;
078: }
079: return null;
080:
081: }
082:
083: public IParsedQuery parseQuery(String name, IndexQuery originalQuery)
084: throws JoftiException {
085:
086: // get text string
087: EJBQuery tempQuery = (EJBQuery) originalQuery;
088:
089: ParsedQuery parsedQuery = null;
090:
091: //see if query is already cached - this ignores parameters - and only checks the String format
092: synchronized (lockObject) {
093: parsedQuery = (ParsedQuery) compiledQueries.get(name);
094: }
095:
096: // we have a cached query here
097: if (parsedQuery == null) {
098: // we have to parse the query
099: if (log.isDebugEnabled()) {
100: log.debug("parsing new query " + tempQuery.getQuery());
101: }
102: // parse the query
103:
104: parsedQuery = parseQuery(new ParsedQuery(), tempQuery
105: .getQuery());
106:
107: parsedQuery.setFirstResult(tempQuery.getFirstResult());
108:
109: //add returned query into cache
110: synchronized (lockObject) {
111: compiledQueries.put(name, parsedQuery);
112: }
113: }
114:
115: // we use a wrapper here as queries are actually cached for reuse
116: // and wrapper is where we set the values - otherwise we would have to
117: // produce a copy of the query
118:
119: ParsedQueryWrapper wrapper = new ParsedQueryWrapper(parsedQuery);
120:
121: // add in the name parameters if we have any
122: wrapper.setNamedValueMap(tempQuery.getParameterMap());
123:
124: wrapper.setFirstResult(tempQuery.getFirstResult());
125: wrapper.setMaxResults(tempQuery.getMaxResults());
126:
127: return wrapper;
128: }
129:
130: private ParsedQuery parseQuery(ParsedQuery parsedQuery,
131: String selectClause) throws JoftiException {
132: Reader input = null;
133: try {
134: input = new StringReader(selectClause);
135:
136: CommonLexer lexer = new CommonLexer(input);
137:
138: EJB3QueryParser parser = new EJB3QueryParser(lexer);
139:
140: parser.queryStatement();
141:
142: AST ast = parser.getAST();
143: AST selectStatement = null;
144:
145: if (ast == null || ast.getFirstChild() == null) {
146:
147: throw new JoftiException(
148: "Query '"
149: + selectClause
150: + "' is not of format 'SELECT variable[[,] variable]* FROM Class [AS] identifier[,Class [AS] identifier] WHERE predicate [ORDER BY identifier [ASC|DESC]]*'");
151: }
152: if (log.isDebugEnabled()) {
153: log.debug(ast.toStringTree());
154: }
155:
156: parsedQuery = parseClause(ast, parsedQuery);
157:
158: } catch (Exception e) {
159: throw new JoftiException(e);
160: } finally {
161: try {
162: input.close();
163: } catch (Exception e) {
164: log.warn("problem closing reader", e);
165: }
166: }
167: return parsedQuery;
168: }
169:
170: private ParsedQuery parseClause(AST ast, ParsedQuery parsedQuery)
171: throws JoftiException {
172: AST orderNode = null;
173: do {
174: //we have no where clause
175: switch (ast.getType()) {
176: case CommonLexerTokenTypes.NAMESPACE:
177: // we need to do from first
178: parsedQuery = parseNameSpace(ast.getFirstChild()
179: .getNextSibling(), parsedQuery);
180:
181: // now reset the node to the where or from clause node
182: ast = ast.getFirstChild();
183: break;
184: case CommonLexerTokenTypes.WHERE:
185: // we need to do from first
186: parsedQuery = parseFrom(ast.getFirstChild()
187: .getFirstChild().getNextSibling(), parsedQuery);
188:
189: // now get the stuff out of the where clause
190:
191: parsedQuery = parseWhere(ast.getFirstChild()
192: .getNextSibling(), parsedQuery);
193:
194: // now reset the node to the select node
195: ast = ast.getFirstChild().getFirstChild();
196: break;
197: // now reset the node to the select node
198: case CommonLexerTokenTypes.FROM:
199: parsedQuery = parseFrom(ast.getFirstChild()
200: .getNextSibling(), parsedQuery);
201:
202: // we can now parse the select part
203: ast = ast.getFirstChild();
204: break;
205: // where is first
206:
207: case CommonLexerTokenTypes.SELECT:
208: parsedQuery = parseSelect(ast, parsedQuery);
209:
210: // we can now parse the select part
211: ast = ast.getFirstChild();
212: break;
213: case CommonLexerTokenTypes.ORDER:
214: orderNode = ast;
215: //mark for later
216: // parsedQuery = parseOrder(ast, parsedQuery);
217: ast = ast.getFirstChild();
218: break;
219: // skip through the remianing arguments in the select
220: default:
221: ast = ast.getFirstChild();
222: break;
223: }
224: } while (ast != null);
225:
226: if (orderNode != null) {
227: parsedQuery = parseOrder(orderNode, parsedQuery);
228: }
229: return parsedQuery;
230: }
231:
232: private ParsedQuery parseWhere(AST ast, ParsedQuery parsedQuery)
233: throws JoftiException {
234: Stack stack = new Stack();
235:
236: stack = parseWherePredicates(ast, stack, parsedQuery);
237:
238: if (log.isDebugEnabled()) {
239: log.debug(stack);
240: }
241:
242: // we need to reverse the stack here
243: Stack temp = new Stack();
244:
245: boolean predicateElement = false;
246:
247: while (stack.size() != 0) {
248: Object obj = stack.pop();
249: if (predicateElement) {
250: if (obj instanceof IPredicate) {
251: throw new JoftiException(
252: "Query not formatted correctly a join operator must seperate two predicates");
253: }
254: predicateElement = false;
255: } else {
256: if (obj instanceof Operator) {
257: throw new JoftiException(
258: "Query not formatted correctly a join operator must seperate two predicates");
259: }
260: predicateElement = true;
261: }
262: temp.push(obj);
263: }
264:
265: parsedQuery.setPredicates(temp);
266: return parsedQuery;
267: }
268:
269: private ParsedQuery parseNameSpace(AST expression, ParsedQuery query)
270: throws JoftiException {
271:
272: AST node = expression;
273: String nameSpace = "";
274: do {
275: if (log.isDebugEnabled()) {
276: log.debug("parsing select for " + node.toStringTree());
277: }
278:
279: nameSpace = nameSpace + node.getText();
280: node = node.getNextSibling();
281:
282: } while (node != null);
283:
284: query.setNameSpace(nameSpace);
285: return query;
286:
287: }
288:
289: private ParsedQuery parseSelect(AST expression, ParsedQuery query)
290: throws JoftiException {
291:
292: // will be alias for field
293: AST node = expression.getFirstChild();
294:
295: do {
296: // we have an as
297:
298: if (log.isDebugEnabled()) {
299: log.debug("parsing select for " + node.toStringTree());
300: }
301: // put the aliases into the alias and return map
302: addToFieldMap(query.getAliasMap(), query
303: .getResultFieldsMap(), node.getText());
304: node = node.getNextSibling();
305:
306: } while (node != null);
307:
308: return query;
309:
310: }
311:
312: private ParsedQuery parseFrom(AST expression, ParsedQuery query)
313: throws JoftiException {
314:
315: //first one must be an alias identifier
316: AST node = expression;
317:
318: do {
319: // we have an as
320: if (node.getType() == CommonLexerTokenTypes.ALIAS_IDENTIFIER) {
321: if (log.isDebugEnabled()) {
322: log.debug("alias found for class "
323: + node.getFirstChild().toStringTree()
324: + " of "
325: + node.getFirstChild().getNextSibling()
326: .toStringTree());
327: }
328:
329: // try and construct a className from the class identifier
330: Class clazz = null;
331: try {
332: clazz = ReflectionUtil.classForName(node
333: .getFirstChild().getText());
334: } catch (Exception e) {
335: throw new JoftiException("Class "
336: + node.getFirstChild().getText()
337: + " cannot be parsed in From clause", e);
338: }
339: if (clazz.isPrimitive() || clazz == Boolean.class) {
340: clazz = introspector.boxPrimitive(clazz);
341: }
342: query.getAliasMap()
343: .put(
344: node.getFirstChild().getNextSibling()
345: .getText(), clazz);
346: node = node.getNextSibling();
347: } else {
348: // if we want to deal with in() we should do it here
349: // even though the AST copes with it I do not think the
350: // feature lends itself to the index query
351:
352: // we have no as - we just use it as normal
353:
354: // not dealing with in stuff here
355: Class clazz = null;
356: try {
357: clazz = ReflectionUtil.classForName(node.getText());
358: } catch (Exception e) {
359: throw new JoftiException("Class " + node.getText()
360: + " cannot be parsed in From clause", e);
361: }
362: if (clazz.isPrimitive() || clazz == Boolean.class) {
363: clazz = introspector.boxPrimitive(clazz);
364: }
365: query.getAliasMap().put(
366: node.getNextSibling().getText(), clazz);
367: //skip the sibling
368: node = node.getNextSibling().getNextSibling();
369: }
370:
371: } while (node != null);
372:
373: return query;
374:
375: }
376:
377: private void addToFieldMap(Map aliasMap, Map returnMap, String alias)
378: throws JoftiException {
379: int aliasSeperator = alias.indexOf('.');
380:
381: if (aliasSeperator > 0) {
382: Class clazz = getClassForAlias(aliasMap, alias);
383: ClassFieldMethods fieldSet = (ClassFieldMethods) returnMap
384: .get(clazz);
385: if (fieldSet == null) {
386: fieldSet = new ClassFieldMethods(clazz, null);
387: if (fieldSet.getFieldMap() == null) {
388: fieldSet.setFieldMap(new LinkedHashMap());
389: }
390: returnMap.put(clazz, fieldSet);
391: }
392:
393: // we need to get the methods for the field -
394: // we do it here as it is only done once for the query
395:
396: String attribute = alias.substring(aliasSeperator + 1,
397: alias.length());
398:
399: if (fieldSet.getFieldMap().containsKey(attribute)) {
400: throw new JoftiException("Field " + attribute
401: + " is already part of the return set for "
402: + clazz);
403: }
404: Object[] methods = introspector.getMethodsForAttribute(
405: clazz, attribute);
406: if (methods == null || methods.length == 0) {
407: // log.warn
408: throw new JoftiException("Attribute " + attribute
409: + " is not valid for " + clazz);
410: } else {
411: fieldSet.getFieldMap().put(attribute, methods);
412: }
413:
414: } else {
415: if (!aliasMap.containsKey(alias)) {
416: throw new JoftiException("select for identifier "
417: + alias + " is not mentioned in from clause");
418: } else if (returnMap.containsKey(aliasMap.get(alias))) {
419: if (returnMap.get(aliasMap.get(alias)) == null
420: || ((ClassFieldMethods) returnMap.get(aliasMap
421: .get(alias))).getFieldMap() == null
422: || ((ClassFieldMethods) returnMap.get(aliasMap
423: .get(alias))).getFieldMap().size() == 0) {
424: throw new JoftiException(
425: ""
426: + aliasMap.get(alias)
427: + " under alias "
428: + alias
429: + " is listed more than once in select clause");
430: } else
431: throw new JoftiException(
432: " "
433: + aliasMap.get(alias)
434: + " as whole object under alias "
435: + alias
436: + " cannot be selected if fields from class are already in select clause");
437: }
438:
439: returnMap.put(aliasMap.get(alias), new ClassFieldMethods(
440: (Class) aliasMap.get(alias), null));
441:
442: }
443: }
444: }
|