001: /*
002: *
003: * Copyright (c) 2003 Adrian Price. All rights reserved.
004: */
005:
006: package org.obe.engine.util;
007:
008: import org.apache.commons.logging.Log;
009: import org.apache.commons.logging.LogFactory;
010: import org.obe.client.api.repository.RepositoryException;
011: import org.obe.engine.WorkflowEngine;
012: import org.obe.engine.WorkflowEngineUtilities;
013: import org.obe.spi.model.AttributeInstance;
014: import org.obe.spi.model.AttributedEntity;
015: import org.obe.spi.service.DataConverter;
016: import org.obe.sql.ParseException;
017: import org.obe.sql.SQLParser;
018: import org.obe.sql.SimpleNode;
019: import org.obe.xpdl.model.data.DataTypes;
020: import org.wfmc.wapi.WMFilter;
021:
022: import java.beans.PropertyDescriptor;
023: import java.io.IOException;
024: import java.io.StringReader;
025: import java.io.StringWriter;
026: import java.lang.reflect.Array;
027: import java.lang.reflect.InvocationTargetException;
028: import java.lang.reflect.Method;
029: import java.util.ArrayList;
030: import java.util.Collection;
031: import java.util.Iterator;
032: import java.util.Map;
033:
034: /**
035: * Implements filtering on OBE attributes and JavaBean properties.
036: *
037: * @author Adrian Price
038: */
039: public class AttributeFilter {
040: private static final Log _logger = LogFactory
041: .getLog(AttributeFilter.class);
042: private static final CollatedComparator _comparator = new CollatedComparator();
043:
044: private AttributeFilter() {
045: }
046:
047: public static Object[] findByFilter(WMFilter[] filters,
048: Class valueClass, Collection values, boolean countFlag)
049: throws RepositoryException {
050:
051: // Check whether any of the filters are SQL-type.
052: boolean sql = false;
053: for (int i = 0; i < filters.length; i++) {
054: if (filters[i].getFilterType() == WMFilter.SQL_TYPE) {
055: sql = true;
056: break;
057: }
058: }
059: if (sql) {
060: return findBySQLFilter(filters, valueClass, values,
061: countFlag);
062: } else {
063: return findByAttribute(filters, valueClass, values,
064: countFlag);
065: }
066: }
067:
068: private static Object[] findByAttribute(WMFilter[] filters,
069: Class valueClass, Collection values, boolean countFlag)
070: throws RepositoryException {
071:
072: // Extract attribute names from simple filters.
073: String[] attributes = getAttributeNames(filters);
074:
075: // Introspect properties for this value class.
076: Map propDescs = WorkflowEngineUtilities.introspectToMap(
077: valueClass, null);
078:
079: // Check the value class to see whether we can compare by OBE attribute.
080: boolean byAttributes = AttributedEntity.class
081: .isAssignableFrom(valueClass);
082:
083: // We need a DataConverter to enable meaningful property comparisons.
084: DataConverter dataConverter = WorkflowEngine.getEngine()
085: .getServiceManager().getDataConverter();
086:
087: // Search the input collection for matches.
088: Collection results = new ArrayList(values.size());
089: try {
090: for (Iterator iter = values.iterator(); iter.hasNext();) {
091: Object candidate = iter.next();
092: boolean matches = true;
093: for (int j = 0; j < filters.length && matches; j++) {
094: WMFilter filter = filters[j];
095: Object candidateValue;
096: Class candidateType;
097: if (byAttributes) {
098: Map attrs = ((AttributedEntity) candidate)
099: .getAttributeInstances();
100: AttributeInstance attrInst = (AttributeInstance) attrs
101: .get(attributes[j]);
102: if (attrInst == null) {
103: matches = false;
104: break;
105: }
106: candidateValue = attrInst.getValue();
107: candidateType = DataTypes.classForType(attrInst
108: .getType());
109: } else {
110: Method getter = ((PropertyDescriptor) propDescs
111: .get(filter.getAttributeName()))
112: .getReadMethod();
113: // Cast required to suppress JDK1.5 varargs compiler warning.
114: candidateValue = getter.invoke(candidate,
115: (Object[]) null);
116: candidateType = getter.getReturnType();
117: }
118: Object filterValue = dataConverter.convertValue(
119: filter.getFilterValue(), candidateType);
120: int result = _comparator.compare(candidateValue,
121: filterValue);
122: switch (filter.getComparison()) {
123: case WMFilter.EQ:
124: matches &= result == 0;
125: break;
126: case WMFilter.GE:
127: matches &= result >= 0;
128: break;
129: case WMFilter.GT:
130: matches &= result > 0;
131: break;
132: case WMFilter.LE:
133: matches &= result <= 0;
134: break;
135: case WMFilter.LT:
136: matches &= result < 0;
137: break;
138: case WMFilter.NE:
139: matches &= result != 0;
140: break;
141: }
142: }
143: if (matches)
144: results.add(candidate);
145: }
146: } catch (IllegalAccessException e) {
147: throw new RepositoryException(e);
148: } catch (IllegalArgumentException e) {
149: throw new RepositoryException(e);
150: } catch (InvocationTargetException e) {
151: throw new RepositoryException(e);
152: }
153: Object[] ret = (Object[]) Array.newInstance(valueClass, results
154: .size());
155: return countFlag ? ret : results.toArray(ret);
156: }
157:
158: private static Object[] findBySQLFilter(WMFilter[] filters,
159: Class valueClass, Collection values, boolean countFlag)
160: throws RepositoryException {
161:
162: StringBuffer sb = new StringBuffer();
163: boolean and = false;
164: for (int i = 0; i < filters.length; i++) {
165: WMFilter filter = filters[i];
166: if (and)
167: sb.append(" AND ");
168: switch (filter.getFilterType()) {
169: case WMFilter.SIMPLE_TYPE:
170: sb.append(filter.getAttributeName()).append(' ');
171: sb.append(filter.getSQLComparison()).append(' ');
172: Object value = filter.getFilterValue();
173: boolean string = value instanceof String;
174: if (string)
175: sb.append('\'');
176: sb.append(value);
177: if (string)
178: sb.append('\'');
179: break;
180: case WMFilter.SQL_TYPE:
181: if (and)
182: sb.append('(');
183: sb.append(filter.getFilterString());
184: if (and)
185: sb.append(')');
186: break;
187: default:
188: throw new IllegalArgumentException(
189: "Unsupported filter type: "
190: + filter.getFilterType());
191: }
192: and = true;
193: }
194: String filterString = sb.toString();
195:
196: if (_logger.isDebugEnabled())
197: _logger.debug("findBySQLFilter: filterString = "
198: + filterString);
199:
200: // Parse the SQL filter expression.
201: SimpleNode root = prepare(filterString);
202:
203: // Apply the filter expression to every value, noting matches.
204: Collection results = new ArrayList(values.size());
205: for (Iterator iter = values.iterator(); iter.hasNext();) {
206: Object candidate = iter.next();
207: Object result = root.execute(candidate);
208: if (Boolean.TRUE.equals(result))
209: results.add(candidate);
210: }
211:
212: Object[] ret = (Object[]) Array.newInstance(valueClass, results
213: .size());
214: return countFlag ? ret : results.toArray(ret);
215: }
216:
217: private static SimpleNode prepare(String filterString)
218: throws RepositoryException {
219:
220: try {
221: SQLParser parser = new SQLParser(new StringReader(
222: filterString));
223: SimpleNode root = parser.SQLOrExpr();
224: if (_logger.isDebugEnabled()) {
225: StringWriter out = new StringWriter();
226: root.write(out);
227: _logger.debug("filter parsed to: " + out.toString());
228: }
229: return root;
230: } catch (IOException e) {
231: throw new RepositoryException(e);
232: } catch (ParseException e) {
233: throw new RepositoryException(e);
234: }
235: }
236:
237: private static String[] getAttributeNames(WMFilter[] filters) {
238: String[] attrNames = new String[filters.length];
239: for (int i = 0; i < filters.length; i++) {
240: if (filters[i].getFilterType() != WMFilter.SIMPLE_TYPE) {
241: throw new UnsupportedOperationException(
242: "SQL and simple filters cannot be combined");
243: }
244: attrNames[i] = filters[i].getAttributeName();
245: }
246: return attrNames;
247: }
248: }
|