001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.xml.filter;
017:
018: import org.geotools.filter.AttributeExpression;
019: import org.geotools.filter.BetweenFilter;
020: import org.geotools.filter.CompareFilter;
021: import org.geotools.filter.Expression;
022: import org.geotools.filter.FidFilter;
023: import org.geotools.filter.Filter;
024: import org.geotools.filter.FilterFactory;
025: import org.geotools.filter.FilterFactoryFinder;
026: import org.geotools.filter.FilterType;
027: import org.geotools.filter.FilterVisitor;
028: import org.geotools.filter.FilterVisitor2;
029: import org.geotools.filter.FunctionExpression;
030: import org.geotools.filter.GeometryFilter;
031: import org.geotools.filter.IllegalFilterException;
032: import org.geotools.filter.LikeFilter;
033: import org.geotools.filter.LiteralExpression;
034: import org.geotools.filter.LogicFilter;
035: import org.geotools.filter.MathExpression;
036: import org.geotools.filter.NullFilter;
037: import org.geotools.xml.XMLHandlerHints;
038: import org.opengis.filter.BinaryLogicOperator;
039: import org.opengis.filter.ExcludeFilter;
040: import org.opengis.filter.IncludeFilter;
041:
042: import java.util.ArrayList;
043: import java.util.Arrays;
044: import java.util.Collections;
045: import java.util.HashSet;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.Set;
049: import java.util.Stack;
050:
051: /**
052: * Prepares a filter for xml encoded for interoperability with another system. It will behave differently depeding on
053: * the compliance level chosen. A new request will have to be made and the features will have
054: * to be tested again on the client side if there are any FidFilters in the filter. Consider the following to understand why:
055: *
056: * and {
057: * nullFilter
058: * or{
059: * fidFilter
060: * nullFilter
061: * }
062: * }
063: *
064: * for strict it would throw an exception, for low it would be left alone, but for Medium it would end up as:
065: *
066: * and{
067: * nullFilter
068: * nullFilter
069: * }
070: *
071: * and getFids() would return the fids in the fidFilter.
072: *
073: * So the final filter would (this is not standard but a common implementation) return the results of the and filter as well as
074: * all the features that match the fids. Which is more than the original filter would accept.
075: *
076: * The XML Document writer can operate at different levels of compliance. The
077: * geotools level is extremely flexible and forgiving.
078: *
079: * <p>
080: * All NOT(FidFilter) are changed to Filter.INCLUDE. So make sure that the filter is processed again on the client with the original
081: * filter
082: * </p>
083: *
084: * For a description of the difference Compliance levels that can be used see
085: * <ul>
086: * <li>{@link XMLHandlerHints#VALUE_FILTER_COMPLIANCE_LOW}</li>
087: * <li>{@link XMLHandlerHints#VALUE_FILTER_COMPLIANCE_MEDIUM}</li>
088: * <li>{@link XMLHandlerHints#VALUE_FILTER_COMPLIANCE_HIGH}</li>
089: * </ul>
090: *
091: * @author Jesse
092: */
093: public class FilterEncodingPreProcessor implements FilterVisitor,
094: FilterVisitor2 {
095: private static final int LOW = 0;
096: private static final int MEDIUM = 1;
097: private static final int HIGH = 2;
098: private int complianceInt;
099: private Stack current = new Stack();
100: FilterFactory ff = FilterFactoryFinder.createFilterFactory();
101: private boolean requiresPostProcessing = false;
102:
103: public FilterEncodingPreProcessor(Integer complianceLevel) {
104: if ((complianceLevel != XMLHandlerHints.VALUE_FILTER_COMPLIANCE_LOW)
105: && (complianceLevel != XMLHandlerHints.VALUE_FILTER_COMPLIANCE_MEDIUM)
106: && (complianceLevel != XMLHandlerHints.VALUE_FILTER_COMPLIANCE_HIGH)) {
107: throw new IllegalArgumentException(
108: "compliance level must be one of: XMLHandlerHints.VALUE_FILTER_COMPLIANCE_LOOSE "
109: + "XMLHandlerHints.VALUE_FILTER_COMPLIANCE_MEDIUM or "
110: + "XMLHandlerHints.VALUE_FILTER_COMPLIANCE_MAXIMUM");
111: }
112:
113: this .complianceInt = complianceLevel.intValue();
114: }
115:
116: /**
117: * Gets the fid filter that contains all the fids.
118: *
119: * @return the fid filter that contains all the fids.
120: */
121: public FidFilter getFidFilter() {
122: FidFilter filter = ff.createFidFilter();
123:
124: if (current.isEmpty()) {
125: return filter;
126: }
127:
128: Data data = (Data) current.peek();
129:
130: if (data.fids.size() > 0) {
131: filter.addAllFids(data.fids);
132: }
133:
134: return filter;
135: }
136:
137: /**
138: * Returns the filter that can be encoded.
139: *
140: * @return the filter that can be encoded.
141: */
142: public org.opengis.filter.Filter getFilter() {
143: if (current.isEmpty())
144: return Filter.EXCLUDE;
145: return ((Data) this .current.peek()).filter;
146: }
147:
148: public void visit(Filter filter) {
149: if (filter instanceof BetweenFilter
150: || filter instanceof BetweenFilter
151: || filter instanceof CompareFilter
152: || filter instanceof GeometryFilter
153: || filter instanceof LikeFilter
154: || filter instanceof LogicFilter
155: || filter instanceof NullFilter
156: || filter instanceof FidFilter) {
157: filter.accept(this );
158: } else {
159: current.push(new Data(filter));
160: }
161: }
162:
163: public void visit(BetweenFilter filter) {
164: current.push(new Data(filter));
165: }
166:
167: public void visit(CompareFilter filter) {
168: current.push(new Data(filter));
169: }
170:
171: public void visit(GeometryFilter filter) {
172: current.push(new Data(filter));
173: }
174:
175: public void visit(LikeFilter filter) {
176: current.push(new Data(filter));
177: }
178:
179: public void visit(LogicFilter filter) {
180: int startSize = current.size();
181:
182: try {
183: switch (this .complianceInt) {
184: case LOW:
185: current.push(new Data(filter));
186:
187: break;
188:
189: case MEDIUM:
190:
191: for (Iterator iter = filter.getFilterIterator(); iter
192: .hasNext();) {
193: Filter component = (Filter) iter.next();
194: component.accept(this );
195: }
196:
197: current.push(createMediumLevelLogicFilter(filter
198: .getFilterType(), startSize));
199:
200: break;
201:
202: case HIGH:
203:
204: for (Iterator iter = filter.getFilterIterator(); iter
205: .hasNext();) {
206: Filter component = (Filter) iter.next();
207: component.accept(this );
208: }
209:
210: current.push(createHighLevelLogicFilter(filter
211: .getFilterType(), startSize));
212:
213: break;
214:
215: default:
216: break;
217: }
218: } catch (Exception e) {
219: if (e instanceof UnsupportedFilterException) {
220: throw (UnsupportedFilterException) e;
221: }
222:
223: throw new UnsupportedFilterException(
224: "Exception creating filter", e);
225: }
226: }
227:
228: private Data createMediumLevelLogicFilter(short filterType,
229: int startOfFilterStack) throws IllegalFilterException {
230: Data resultingFilter;
231:
232: switch (filterType) {
233: case FilterType.LOGIC_AND: {
234: Set fids = andFids(startOfFilterStack);
235: resultingFilter = buildFilter(filterType,
236: startOfFilterStack);
237: resultingFilter.fids.addAll(fids);
238:
239: if (resultingFilter.filter != Filter.EXCLUDE
240: && !fids.isEmpty())
241: requiresPostProcessing = true;
242: break;
243: }
244:
245: case FilterType.LOGIC_OR: {
246: Set fids = orFids(startOfFilterStack);
247: resultingFilter = buildFilter(filterType,
248: startOfFilterStack);
249: resultingFilter.fids.addAll(fids);
250: break;
251: }
252:
253: case FilterType.LOGIC_NOT:
254: resultingFilter = buildFilter(filterType,
255: startOfFilterStack);
256: break;
257:
258: default:
259: resultingFilter = buildFilter(filterType,
260: startOfFilterStack);
261:
262: break;
263: }
264:
265: return resultingFilter;
266: }
267:
268: private Set orFids(int startOfFilterStack) {
269: Set set = new HashSet();
270:
271: for (int i = startOfFilterStack; i < current.size(); i++) {
272: Data data = (Data) current.get(i);
273:
274: if (!data.fids.isEmpty()) {
275: set.addAll(data.fids);
276: }
277: }
278:
279: return set;
280: }
281:
282: private Set andFids(int startOfFilterStack) {
283: if (!hasFidFilter(startOfFilterStack)) {
284: return Collections.EMPTY_SET;
285: }
286:
287: Set toRemove = new HashSet();
288: List fidSet = new ArrayList();
289: boolean doRemove = true;
290:
291: for (int i = startOfFilterStack; i < current.size(); i++) {
292: Data data = (Data) current.get(i);
293:
294: if (data.fids.isEmpty()) {
295: toRemove.add(data);
296: } else {
297: fidSet.add(data.fids);
298:
299: if (data.filter != Filter.EXCLUDE) {
300: doRemove = false;
301: }
302: }
303: }
304:
305: if (doRemove) {
306: current.removeAll(toRemove);
307: }
308:
309: if (fidSet.size() == 0) {
310: return Collections.EMPTY_SET;
311: }
312:
313: if (fidSet.size() == 1) {
314: return (Set) fidSet.get(0);
315: }
316:
317: HashSet set = new HashSet();
318:
319: for (int i = 0; i < fidSet.size(); i++) {
320: Set tmp = (Set) fidSet.get(i);
321:
322: for (Iterator iter = tmp.iterator(); iter.hasNext();) {
323: String fid = (String) iter.next();
324:
325: if (allContain(fid, fidSet)) {
326: set.add(fid);
327: }
328: }
329: }
330:
331: return set;
332: }
333:
334: private boolean allContain(String fid, List fidSets) {
335: for (int i = 0; i < fidSets.size(); i++) {
336: Set tmp = (Set) fidSets.get(i);
337:
338: if (!tmp.contains(fid)) {
339: return false;
340: }
341: }
342:
343: return true;
344: }
345:
346: private Data buildFilter(short filterType, int startOfFilterStack)
347: throws IllegalFilterException {
348: if (current.isEmpty()) {
349: return Data.ALL;
350: }
351:
352: if (filterType == FilterType.LOGIC_NOT) {
353: return buildNotFilter(startOfFilterStack);
354: }
355:
356: if (current.size() == (startOfFilterStack + 1)) {
357: return (Data) current.pop();
358: }
359:
360: LogicFilter f = ff.createLogicFilter(filterType);
361:
362: while (current.size() > startOfFilterStack) {
363: Data data = (Data) current.pop();
364:
365: if (data.filter != Filter.EXCLUDE) {
366: f.addFilter(data.filter);
367: }
368: }
369:
370: return new Data(compressFilter(filterType, f));
371: }
372:
373: private org.opengis.filter.Filter compressFilter(short filterType,
374: LogicFilter f) throws IllegalFilterException {
375: LogicFilter result;
376: int added = 0;
377:
378: switch (filterType) {
379: case FilterType.LOGIC_AND:
380:
381: if (contains(f, Filter.EXCLUDE)) {
382: return Filter.EXCLUDE;
383: }
384:
385: result = ff.createLogicFilter(filterType);
386:
387: for (Iterator iter = f.getFilterIterator(); iter.hasNext();) {
388: org.opengis.filter.Filter filter = (org.opengis.filter.Filter) iter
389: .next();
390:
391: if (filter == Filter.INCLUDE) {
392: continue;
393: }
394:
395: added++;
396: result.addFilter(filter);
397: }
398:
399: if (!result.getFilterIterator().hasNext()) {
400: return Filter.EXCLUDE;
401: }
402:
403: break;
404:
405: case FilterType.LOGIC_OR:
406:
407: if (contains(f, Filter.INCLUDE)) {
408: return Filter.INCLUDE;
409: }
410:
411: result = ff.createLogicFilter(filterType);
412:
413: for (Iterator iter = f.getFilterIterator(); iter.hasNext();) {
414: Filter filter = (Filter) iter.next();
415:
416: if (filter == org.geotools.filter.Filter.ALL) {
417: continue;
418: }
419:
420: added++;
421: result.addFilter(filter);
422: }
423:
424: if (!result.getFilterIterator().hasNext()) {
425: return Filter.EXCLUDE;
426: }
427:
428: break;
429:
430: default:
431: return Filter.EXCLUDE;
432: }
433:
434: switch (added) {
435: case 0:
436: return Filter.EXCLUDE;
437:
438: case 1:
439: return (Filter) result.getFilterIterator().next();
440:
441: default:
442: return result;
443: }
444: }
445:
446: private boolean contains(BinaryLogicOperator f,
447: org.opengis.filter.Filter toFind) {
448: for (Iterator iter = f.getChildren().iterator(); iter.hasNext();) {
449: if (toFind.equals(iter.next())) {
450: return true;
451: }
452: }
453: return false;
454: }
455:
456: private Data buildNotFilter(int startOfFilterStack) {
457: if (current.size() > (startOfFilterStack + 1)) {
458: throw new UnsupportedFilterException(
459: "A not filter cannot have more than one filter");
460: } else {
461: Data tmp = (Data) current.pop();
462:
463: Data data = new Data(ff.not(tmp.filter));
464:
465: if (!tmp.fids.isEmpty()) {
466: data.filter = Filter.NONE;
467: data.fids.clear();
468: requiresPostProcessing = true;
469: }
470:
471: return data;
472: }
473: }
474:
475: private Data createHighLevelLogicFilter(short filterType,
476: int startOfFilterStack) throws IllegalFilterException {
477: if (hasFidFilter(startOfFilterStack)) {
478: Set fids;
479:
480: switch (filterType) {
481: case FilterType.LOGIC_AND:
482: fids = andFids(startOfFilterStack);
483:
484: Data filter = buildFilter(filterType,
485: startOfFilterStack);
486: filter.fids.addAll(fids);
487:
488: return filter;
489:
490: case FilterType.LOGIC_OR: {
491: if (hasNonFidFilter(startOfFilterStack)) {
492: throw new UnsupportedFilterException(
493: "Maximum compliance does not allow Logic filters to contain FidFilters");
494: }
495:
496: fids = orFids(startOfFilterStack);
497:
498: pop(startOfFilterStack);
499:
500: Data data = new Data();
501: data.fids.addAll(fids);
502:
503: return data;
504: }
505:
506: case FilterType.LOGIC_NOT:
507: return buildFilter(filterType, startOfFilterStack);
508:
509: default:
510: return Data.ALL;
511: }
512: } else {
513: return buildFilter(filterType, startOfFilterStack);
514: }
515: }
516:
517: private void pop(int startOfFilterStack) {
518: while (current.size() > startOfFilterStack)
519: current.pop();
520: }
521:
522: private boolean hasNonFidFilter(int startOfFilterStack) {
523: for (int i = startOfFilterStack; i < current.size(); i++) {
524: Data data = (Data) current.get(i);
525:
526: if (data.filter != Filter.EXCLUDE) {
527: return true;
528: }
529: }
530:
531: return false;
532: }
533:
534: private boolean hasFidFilter(int startOfFilterStack) {
535: for (int i = startOfFilterStack; i < current.size(); i++) {
536: Data data = (Data) current.get(i);
537:
538: if (!data.fids.isEmpty()) {
539: return true;
540: }
541: }
542:
543: return false;
544: }
545:
546: public void visit(NullFilter filter) {
547: current.push(new Data(filter));
548: }
549:
550: public void visit(FidFilter filter) {
551: Data data = new Data();
552: data.fids.addAll(Arrays.asList(filter.getFids()));
553: current.push(data);
554: }
555:
556: public void visit(AttributeExpression expression) {
557: // nothing todo
558: }
559:
560: public void visit(Expression expression) {
561: // nothing todo
562: }
563:
564: public void visit(LiteralExpression expression) {
565: // nothing todo
566: }
567:
568: public void visit(MathExpression expression) {
569: // nothing todo
570: }
571:
572: public void visit(FunctionExpression expression) {
573: // nothing todo
574: }
575:
576: public void visit(IncludeFilter filter) {
577: current.push(new Data(filter));
578: }
579:
580: public void visit(ExcludeFilter filter) {
581: current.push(new Data(filter));
582: }
583:
584: private static class Data {
585: final public static Data NONE = new Data(Filter.EXCLUDE);
586: final public static Data ALL = new Data(Filter.INCLUDE);
587: final Set fids = new HashSet();
588: org.opengis.filter.Filter filter;
589:
590: public Data() {
591: this (Filter.ALL);
592: }
593:
594: public Data(org.opengis.filter.Filter f) {
595: filter = f;
596: }
597:
598: public Data(Filter f) {
599: filter = f;
600: }
601:
602: public String toString() {
603: return filter + ":" + fids;
604: }
605: }
606:
607: /**
608: * Returns true if the filter was one where the request to the server is more general than the actual filter.
609: * See {@link XMLHandlerHints#VALUE_FILTER_COMPLIANCE_MEDIUM} and example of when this can happen.
610: *
611: * @return true if the filter was one where the request to the server is more general than the actual filter.
612: */
613: public boolean requiresPostProcessing() {
614: return requiresPostProcessing;
615: }
616:
617: }
|