001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/sde/SDEWhereBuilder.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2006 by: M.O.S.S. Computer Grafik Systeme GmbH
006     Hohenbrunner Weg 13
007     D-82024 Taufkirchen
008     http://www.moss.de/
009    
010     This library is free software; you can redistribute it and/or
011     modify it under the terms of the GNU Lesser General Public
012     License as published by the Free Software Foundation; either
013     version 2.1 of the License, or (at your option) any later version.
014    
015     This library is distributed in the hope that it will be useful,
016     but WITHOUT ANY WARRANTY; without even the implied warranty of
017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
018     Lesser General Public License for more details.
019    
020     You should have received a copy of the GNU Lesser General Public
021     License along with this library; if not, write to the Free Software
022     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
023    
024     ---------------------------------------------------------------------------*/
025    package org.deegree.io.datastore.sde;
026    
027    import java.sql.Types;
028    import java.util.ArrayList;
029    import java.util.Iterator;
030    import java.util.List;
031    
032    import org.deegree.framework.log.ILogger;
033    import org.deegree.framework.log.LoggerFactory;
034    import org.deegree.io.datastore.DatastoreException;
035    import org.deegree.io.datastore.PropertyPathResolvingException;
036    import org.deegree.io.datastore.schema.MappedFeatureType;
037    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
038    import org.deegree.io.datastore.schema.MappedPropertyType;
039    import org.deegree.io.datastore.schema.MappedSimplePropertyType;
040    import org.deegree.io.datastore.schema.content.MappingField;
041    import org.deegree.io.datastore.schema.content.MappingGeometryField;
042    import org.deegree.io.datastore.schema.content.SimpleContent;
043    import org.deegree.io.datastore.sql.TableAliasGenerator;
044    import org.deegree.io.datastore.sql.wherebuilder.GeometryPropertyNode;
045    import org.deegree.io.datastore.sql.wherebuilder.PropertyNode;
046    import org.deegree.io.datastore.sql.wherebuilder.QueryTableTree;
047    import org.deegree.io.datastore.sql.wherebuilder.SimplePropertyNode;
048    import org.deegree.io.sdeapi.SDEAdapter;
049    import org.deegree.model.filterencoding.ArithmeticExpression;
050    import org.deegree.model.filterencoding.ComparisonOperation;
051    import org.deegree.model.filterencoding.ComplexFilter;
052    import org.deegree.model.filterencoding.DBFunction;
053    import org.deegree.model.filterencoding.Expression;
054    import org.deegree.model.filterencoding.ExpressionDefines;
055    import org.deegree.model.filterencoding.FeatureFilter;
056    import org.deegree.model.filterencoding.Filter;
057    import org.deegree.model.filterencoding.FilterTools;
058    import org.deegree.model.filterencoding.Function;
059    import org.deegree.model.filterencoding.Literal;
060    import org.deegree.model.filterencoding.LogicalOperation;
061    import org.deegree.model.filterencoding.Operation;
062    import org.deegree.model.filterencoding.OperationDefines;
063    import org.deegree.model.filterencoding.PropertyIsBetweenOperation;
064    import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
065    import org.deegree.model.filterencoding.PropertyIsLikeOperation;
066    import org.deegree.model.filterencoding.PropertyIsNullOperation;
067    import org.deegree.model.filterencoding.PropertyName;
068    import org.deegree.model.filterencoding.SpatialOperation;
069    import org.deegree.ogcbase.PropertyPath;
070    
071    import com.esri.sde.sdk.client.SeCoordinateReference;
072    import com.esri.sde.sdk.client.SeFilter;
073    import com.esri.sde.sdk.client.SeLayer;
074    import com.esri.sde.sdk.client.SeShape;
075    import com.esri.sde.sdk.client.SeShapeFilter;
076    
077    /**
078     * <code>WhereBuilder</code> implementation for ArcSDE.
079     * 
080     * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a>
081     * @author last edited by: $Author: apoth $
082     * 
083     * @version $Revision: 8138 $, $Date: 2007-09-12 15:52:14 +0200 (Mi, 12 Sep 2007) $
084     */
085    public class SDEWhereBuilder {
086    
087        protected static final ILogger LOG = LoggerFactory.getLogger( SDEWhereBuilder.class );
088    
089        protected MappedFeatureType rootFeatureType;
090    
091        protected Filter filter;
092    
093        protected QueryTableTree queryTableTree;
094    
095        protected List<PropertyPath> filterPropertyPaths = new ArrayList<PropertyPath>();
096    
097        /**
098         * Creates a new instance of <code>SDEWhereBuilder</code> for the given parameters.
099         * 
100         * @param rootFts
101         *            selected feature types, more than one type means that the types are joined
102         * @param aliases
103         *            aliases for the feature types, may be null (must have same length as rootFts
104         *            otherwise)
105         * @param filter
106         * @param aliasGenerator
107         * @throws DatastoreException
108         */
109        public SDEWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter,
110                                TableAliasGenerator aliasGenerator ) throws DatastoreException {
111    
112            this.rootFeatureType = rootFts [0];
113            this.queryTableTree = new QueryTableTree( rootFts, aliases, aliasGenerator );
114            this.filter = filter;
115            if ( filter != null ) {
116                if ( !( filter instanceof ComplexFilter || filter instanceof FeatureFilter ) ) {
117                    throw new DatastoreException( "Invalid filter type: '" + filter.getClass()
118                                                  + "'. Filter must be a ComplexFilter or a FeatureFilter." );
119                }
120                buildFilterPropertyNameMap();
121                for ( PropertyPath property : this.filterPropertyPaths ) {
122                    this.queryTableTree.addFilterProperty( property );
123                }
124            }
125        }
126    
127        /**
128         * @return
129         */
130        public String getRootTableAlias() {
131            return this.queryTableTree.getRootAlias();
132        }
133    
134        /**
135         * @return filter
136         */
137        public Filter getFilter() {
138            return this.filter;
139        }
140    
141        /**
142         * Returns the internal (database specific) SRS code used in the geometry field of the given
143         * <code>SpatialOperation</code>.
144         * 
145         * @param operation
146         *            <code>SpatialOperation</code> for which the internal SRS is needed
147         * @return the internal (database specific) SRS code.
148         */
149        protected int getInternalSRS( SpatialOperation operation ) {
150            PropertyPath propertyPath = operation.getPropertyName().getValue();
151            PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath );
152            if ( propertyNode == null ) {
153                String msg = "Internal error in WhereBuilder: no PropertyNode for path '" + propertyPath
154                             + "' in QueryTableTree.";
155                LOG.logError( msg );
156                throw new RuntimeException( msg );
157            } else if ( !( propertyNode instanceof GeometryPropertyNode ) ) {
158                String msg = "Internal error in WhereBuilder: unexpected PropertyNode type: '"
159                             + propertyNode.getClass().getName() + "'. Must be a GeometryPropertyNode.";
160                LOG.logError( msg );
161                throw new RuntimeException( msg );
162            }
163            MappedGeometryPropertyType gpc = (MappedGeometryPropertyType) propertyNode.getProperty();
164            MappingGeometryField field = gpc.getMappingField();
165            return field.getSRS();
166        }
167    
168        /**
169         * @param propertyName
170         * @return
171         */
172        protected int getPropertyNameSQLType( PropertyName propertyName ) {
173            PropertyPath propertyPath = propertyName.getValue();
174            PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath );
175            if ( propertyNode == null ) {
176                String msg = "Internal error in WhereBuilder: no PropertyNode for path '" + propertyPath
177                             + "' in QueryTableTree.";
178                LOG.logError( msg );
179                throw new RuntimeException( msg );
180            }
181            MappedPropertyType propertyType = propertyNode.getProperty();
182            if ( !( propertyType instanceof MappedSimplePropertyType ) ) {
183                String msg = "Error in WhereBuilder: cannot compare against properties of type '" + propertyType.getClass()
184                             + "'.";
185                LOG.logError( msg );
186                throw new RuntimeException( msg );
187            }
188    
189            SimpleContent content = ( (MappedSimplePropertyType) propertyType ).getContent();
190            if ( !( content instanceof MappingField ) ) {
191                String msg = "Virtual properties are currently ignored in SDEWhereBuilder#getPropertyNameSQLType(PropertyName).";
192                LOG.logError( msg );
193                return Types.VARCHAR;
194            }
195    
196            int targetSqlType = ( (MappingField) content ).getType();
197            return targetSqlType;
198        }
199    
200        /**
201         * @throws PropertyPathResolvingException
202         */
203        protected void buildFilterPropertyNameMap()
204                                throws PropertyPathResolvingException {
205            if ( this.filter instanceof ComplexFilter ) {
206                buildPropertyNameMapFromOperation( ( (ComplexFilter) this.filter ).getOperation() );
207            } else if ( this.filter instanceof FeatureFilter ) {
208                // FeatureFilter doesn't have real properties, so we don't have to add them here
209                // maybe for join tables and table aliases we need some auxiliary constructions???
210                // throw new PropertyPathResolvingException( "FeatureFilter not implemented yet." );
211            }
212        }
213    
214        private void buildPropertyNameMapFromOperation( Operation operation )
215                                throws PropertyPathResolvingException {
216            switch ( OperationDefines.getTypeById( operation.getOperatorId() ) ) {
217            case OperationDefines.TYPE_SPATIAL: {
218                registerPropertyName( ( (SpatialOperation) operation ).getPropertyName() );
219                break;
220            }
221            case OperationDefines.TYPE_COMPARISON: {
222                buildPropertyNameMap( (ComparisonOperation) operation );
223                break;
224            }
225            case OperationDefines.TYPE_LOGICAL: {
226                buildPropertyNameMap( (LogicalOperation) operation );
227                break;
228            }
229            default: {
230                break;
231            }
232            }
233        }
234    
235        private void buildPropertyNameMap( ComparisonOperation operation )
236                                throws PropertyPathResolvingException {
237            switch ( operation.getOperatorId() ) {
238            case OperationDefines.PROPERTYISEQUALTO:
239            case OperationDefines.PROPERTYISLESSTHAN:
240            case OperationDefines.PROPERTYISGREATERTHAN:
241            case OperationDefines.PROPERTYISLESSTHANOREQUALTO:
242            case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: {
243                buildPropertyNameMap( ( (PropertyIsCOMPOperation) operation ).getFirstExpression() );
244                buildPropertyNameMap( ( (PropertyIsCOMPOperation) operation ).getSecondExpression() );
245                break;
246            }
247            case OperationDefines.PROPERTYISLIKE: {
248                registerPropertyName( ( (PropertyIsLikeOperation) operation ).getPropertyName() );
249                break;
250            }
251            case OperationDefines.PROPERTYISNULL: {
252                buildPropertyNameMap( ( (PropertyIsNullOperation) operation ).getPropertyName() );
253                break;
254            }
255            case OperationDefines.PROPERTYISBETWEEN: {
256                buildPropertyNameMap( ( (PropertyIsBetweenOperation) operation ).getLowerBoundary() );
257                buildPropertyNameMap( ( (PropertyIsBetweenOperation) operation ).getUpperBoundary() );
258                registerPropertyName( ( (PropertyIsBetweenOperation) operation ).getPropertyName() );
259                break;
260            }
261            default: {
262                break;
263            }
264            }
265        }
266    
267        private void buildPropertyNameMap( LogicalOperation operation )
268                                throws PropertyPathResolvingException {
269            List operationList = operation.getArguments();
270            Iterator it = operationList.iterator();
271            while ( it.hasNext() ) {
272                buildPropertyNameMapFromOperation( (Operation) it.next() );
273            }
274        }
275    
276        private void buildPropertyNameMap( Expression expression )
277                                throws PropertyPathResolvingException {
278            switch ( expression.getExpressionId() ) {
279            case ExpressionDefines.PROPERTYNAME: {
280                registerPropertyName( (PropertyName) expression );
281                break;
282            }
283            case ExpressionDefines.ADD:
284            case ExpressionDefines.SUB:
285            case ExpressionDefines.MUL:
286            case ExpressionDefines.DIV: {
287                buildPropertyNameMap( ( (ArithmeticExpression) expression ).getFirstExpression() );
288                buildPropertyNameMap( ( (ArithmeticExpression) expression ).getSecondExpression() );
289                break;
290            }
291            case ExpressionDefines.FUNCTION: {
292                // TODO: What about PropertyNames used here?
293                break;
294            }
295            case ExpressionDefines.EXPRESSION:
296            case ExpressionDefines.LITERAL: {
297                break;
298            }
299            }
300        }
301    
302        private void registerPropertyName( PropertyName propertyName ) {
303            this.filterPropertyPaths.add( propertyName.getValue() );
304        }
305    
306        /*
307         * appendJoinTableList => String[] der Tabellennamen (mehrfach vorkommende Namen nicht erlaubt)
308         * appendOuterJoins => mit SDE bei versionierten Tabellen realisierbar???
309         */
310    
311        /**
312         * Appends the SQL condition from the <code>Filter</code> to the given sql statement.
313         * 
314         * @param whereCondition
315         */
316        public final void appendWhereCondition( StringBuffer whereCondition ) {
317            if ( filter instanceof ComplexFilter ) {
318                appendComplexFilterAsSQL( whereCondition, (ComplexFilter) filter );
319            } else if ( filter instanceof FeatureFilter ) {
320                FeatureFilter featureFilter = (FeatureFilter) filter;
321                if ( featureFilter.getFeatureIds().size() > 0 ) {
322                    appendFeatureFilterAsSQL( whereCondition, featureFilter );
323                }
324            } else {
325                // assert false : "Unexpected filter type.";
326            }
327        }
328    
329        /**
330         * Appends an SQL fragment for the given object.
331         * 
332         * @param query
333         * @param filter
334         */
335        protected void appendComplexFilterAsSQL( StringBuffer query, ComplexFilter filter ) {
336            appendOperationAsSQL( query, filter.getOperation() );
337        }
338    
339        /**
340         * Appends an SQL fragment for the given object to the given sql statement.
341         * 
342         * @param query
343         * @param operation
344         */
345        protected void appendOperationAsSQL( StringBuffer query, Operation operation ) {
346    
347            switch ( OperationDefines.getTypeById( operation.getOperatorId() ) ) {
348            case OperationDefines.TYPE_SPATIAL: {
349                // handled seperately with buildSpatialFilter()
350                break;
351            }
352            case OperationDefines.TYPE_COMPARISON: {
353                appendComparisonOperationAsSQL( query, (ComparisonOperation) operation );
354                break;
355            }
356            case OperationDefines.TYPE_LOGICAL: {
357                appendLogicalOperationAsSQL( query, (LogicalOperation) operation );
358                break;
359            }
360            default: {
361                break;
362            }
363            }
364        }
365    
366        /**
367         * Appends an SQL fragment for the given object to the given sql statement.
368         * 
369         * @param query
370         * @param operation
371         */
372        protected void appendComparisonOperationAsSQL( StringBuffer query, ComparisonOperation operation ) {
373            switch ( operation.getOperatorId() ) {
374            case OperationDefines.PROPERTYISEQUALTO:
375            case OperationDefines.PROPERTYISLESSTHAN:
376            case OperationDefines.PROPERTYISGREATERTHAN:
377            case OperationDefines.PROPERTYISLESSTHANOREQUALTO:
378            case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: {
379                appendPropertyIsCOMPOperationAsSQL( query, (PropertyIsCOMPOperation) operation );
380                break;
381            }
382            case OperationDefines.PROPERTYISLIKE: {
383                appendPropertyIsLikeOperationAsSQL( query, (PropertyIsLikeOperation) operation );
384                break;
385            }
386            case OperationDefines.PROPERTYISNULL: {
387                appendPropertyIsNullOperationAsSQL( query, (PropertyIsNullOperation) operation );
388                break;
389            }
390            case OperationDefines.PROPERTYISBETWEEN: {
391                appendPropertyIsBetweenOperationAsSQL( query, (PropertyIsBetweenOperation) operation );
392                break;
393            }
394            }
395        }
396    
397        /**
398         * Appends an SQL fragment for the given object to the given sql statement.
399         * 
400         * @param query
401         * @param operation
402         */
403        protected void appendPropertyIsCOMPOperationAsSQL( StringBuffer query, PropertyIsCOMPOperation operation ) {
404            Expression firstExpr = operation.getFirstExpression();
405            if ( !( firstExpr instanceof PropertyName ) ) {
406                throw new IllegalArgumentException( "First expression in a comparison must "
407                                                    + "always be a 'PropertyName' element." );
408            }
409            int targetSqlType = getPropertyNameSQLType( (PropertyName) firstExpr );
410            if ( operation.isMatchCase() ) {
411                appendExpressionAsSQL( query, firstExpr, targetSqlType );
412            } else {
413                List<Expression> list = new ArrayList<Expression>();
414                list.add( firstExpr );
415                Function func = new DBFunction( "LOWER", list );
416                appendFunctionAsSQL( query, func, targetSqlType );
417            }
418            switch ( operation.getOperatorId() ) {
419            case OperationDefines.PROPERTYISEQUALTO: {
420                query.append( " = " );
421                break;
422            }
423            case OperationDefines.PROPERTYISLESSTHAN: {
424                query.append( " < " );
425                break;
426            }
427            case OperationDefines.PROPERTYISGREATERTHAN: {
428                query.append( " > " );
429                break;
430            }
431            case OperationDefines.PROPERTYISLESSTHANOREQUALTO: {
432                query.append( " <= " );
433                break;
434            }
435            case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: {
436                query.append( " >= " );
437                break;
438            }
439            }
440            if ( operation.isMatchCase() ) {
441                appendExpressionAsSQL( query, operation.getSecondExpression(), targetSqlType );
442            } else {
443                List<Expression> list = new ArrayList<Expression>();
444                list.add( operation.getSecondExpression() );
445                Function func = new DBFunction( "LOWER", list );
446                appendFunctionAsSQL( query, func, targetSqlType );
447            }
448        }
449    
450        /**
451         * Appends an SQL fragment for the given object to the given sql statement. Replacing and escape
452         * handling is based on a finite automaton with 2 states:
453         * <p>
454         * (escapeMode)
455         * <ul>
456         * <li>' is appended as \', \ is appended as \\</li>
457         * <li>every character (including the escapeChar) is simply appended</li>
458         * <li>- unset escapeMode</li>
459         * (escapeMode is false)
460         * </ul>
461         * <ul>
462         * <li>' is appended as \', \ is appended as \\</li>
463         * <li>escapeChar means: skip char, set escapeMode</li>
464         * <li>wildCard means: append %</li>
465         * <li>singleChar means: append ?</li>
466         * </ul>
467         * </p>
468         * 
469         * NOTE: Currently, the method uses a quirk and appends the generated argument inline, i.e. not
470         * using query.addArgument(). This is because of a problem that occurred for example in
471         * Postgresql; the execution of the inline version is *much* faster (at least with version 8.0).
472         * 
473         * @param query
474         * @param operation
475         */
476        protected void appendPropertyIsLikeOperationAsSQL( StringBuffer query, PropertyIsLikeOperation operation ) {
477    
478            String literal = operation.getLiteral().getValue();
479            char escapeChar = operation.getEscapeChar();
480            char wildCard = operation.getWildCard();
481            char singleChar = operation.getSingleChar();
482            boolean escapeMode = false;
483            int length = literal.length();
484            int targetSqlType = getPropertyNameSQLType( operation.getPropertyName() );
485            if ( operation.isMatchCase() ) {
486                appendPropertyNameAsSQL( query, operation.getPropertyName() );
487            } else {
488                List<PropertyName> list = new ArrayList<PropertyName>();
489                list.add( operation.getPropertyName() );
490                Function func = new DBFunction( "LOWER", list );
491                appendFunctionAsSQL( query, func, targetSqlType );
492            }
493            query.append( " LIKE '" );
494            StringBuffer parameter = new StringBuffer();
495            for ( int i = 0; i < length; i++ ) {
496                char c = literal.charAt( i );
497                if ( escapeMode ) {
498                    // ' must (even in escapeMode) be converted to \'
499                    if ( c == '\'' )
500                        parameter.append( "\'" );
501                    // \ must (even in escapeMode) be converted to \\
502                    else if ( c == '\\' )
503                        parameter.append( "\\\\" );
504                    else
505                        parameter.append( c );
506                    escapeMode = false;
507                } else {
508                    // escapeChar means: switch to escapeMode
509                    if ( c == escapeChar )
510                        escapeMode = true;
511                    // wildCard must be converted to %
512                    else if ( c == wildCard )
513                        parameter.append( '%' );
514                    // singleChar must be converted to ?
515                    else if ( c == singleChar )
516                        parameter.append( '?' );
517                    // ' must be converted to \'
518                    else if ( c == '\'' )
519                        parameter.append( "$'$" );
520                    // % must be converted to \'
521                    else if ( c == '%' )
522                        parameter.append( "$%$" );
523                    // ? must be converted to \'
524                    // else if (c == '?') sb.append("$?$");
525                    // \ must (even in escapeMode) be converted to \\
526                    else if ( c == '\\' )
527                        parameter.append( "\\\\" );
528                    else
529                        parameter.append( c );
530                }
531            }
532            if ( operation.isMatchCase() ) {
533                query.append( parameter );
534            } else {
535                query.append( parameter.toString().toLowerCase() );
536            }
537            query.append( '\'' );
538            // query.addArgument( parameter.toString() );
539        }
540    
541        /**
542         * Appends an SQL fragment for the given object to the given sql statement.
543         * 
544         * @param query
545         * @param operation
546         */
547        protected void appendPropertyIsNullOperationAsSQL( StringBuffer query, PropertyIsNullOperation operation ) {
548            appendPropertyNameAsSQL( query, operation.getPropertyName() );
549            query.append( " IS NULL" );
550        }
551    
552        /**
553         * Appends an SQL fragment for the given object to the given sql statement.
554         * 
555         * @param query
556         * @param operation
557         */
558        protected void appendPropertyIsBetweenOperationAsSQL( StringBuffer query, PropertyIsBetweenOperation operation ) {
559    
560            PropertyName propertyName = operation.getPropertyName();
561            int targetSqlType = getPropertyNameSQLType( propertyName );
562            appendExpressionAsSQL( query, operation.getLowerBoundary(), targetSqlType );
563            query.append( " <= " );
564            appendPropertyNameAsSQL( query, propertyName );
565            query.append( " AND " );
566            appendPropertyNameAsSQL( query, propertyName );
567            query.append( " <= " );
568            appendExpressionAsSQL( query, operation.getUpperBoundary(), targetSqlType );
569        }
570    
571        /**
572         * Appends an SQL fragment for the given object to the given sql statement.
573         * 
574         * @param query
575         * @param expression
576         * @param targetSqlType
577         *            sql type code to be used for literals at the bottom of the expression tree
578         */
579        protected void appendExpressionAsSQL( StringBuffer query, Expression expression, int targetSqlType ) {
580            switch ( expression.getExpressionId() ) {
581            case ExpressionDefines.PROPERTYNAME: {
582                appendPropertyNameAsSQL( query, (PropertyName) expression );
583                break;
584            }
585            case ExpressionDefines.LITERAL: {
586                appendLiteralAsSQL( query, (Literal) expression, targetSqlType );
587                break;
588            }
589            case ExpressionDefines.FUNCTION: {
590                Function function = (Function) expression;
591                appendFunctionAsSQL( query, function, targetSqlType );
592                break;
593            }
594            case ExpressionDefines.ADD:
595            case ExpressionDefines.SUB:
596            case ExpressionDefines.MUL:
597            case ExpressionDefines.DIV: {
598                appendArithmeticExpressionAsSQL( query, (ArithmeticExpression) expression, targetSqlType );
599                break;
600            }
601            case ExpressionDefines.EXPRESSION:
602            default: {
603                throw new IllegalArgumentException( "Unexpected expression type: " + expression.getExpressionName() );
604            }
605            }
606        }
607    
608        /**
609         * Appends an SQL fragment for the given object to the given sql statement.
610         * 
611         * @param query
612         * @param literal
613         * @param targetSqlType
614         */
615        protected void appendLiteralAsSQL( StringBuffer query, Literal literal, int targetSqlType ) {
616            switch ( targetSqlType ) {
617            case java.sql.Types.DECIMAL:
618            case java.sql.Types.DOUBLE:
619            case java.sql.Types.FLOAT:
620            case java.sql.Types.INTEGER:
621            case java.sql.Types.NUMERIC:
622            case java.sql.Types.REAL:
623            case java.sql.Types.SMALLINT:
624            case java.sql.Types.TINYINT:
625                query.append( literal.getValue() );
626                break;
627            default:
628                query.append( "'" + literal.getValue() + "'" );
629                break;
630            }
631        }
632    
633        /**
634         * Appends an SQL fragment for the given object to the given sql statement.
635         * 
636         * @param propertyName
637         * @return
638         */
639        protected MappingField getPropertyNameMapping( PropertyName propertyName ) {
640    
641            PropertyPath propertyPath = propertyName.getValue();
642            LOG.logDebug( "Looking up '" + propertyPath + "' in the query table tree." );
643            MappingField mappingField = null;
644            PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath );
645            if ( propertyNode == null ) {
646                String msg = "Internal error in WhereBuilder: no PropertyNode for path '" + propertyPath
647                             + "' in QueryTableTree.";
648                LOG.logError( msg );
649                throw new RuntimeException( msg );
650            } else if ( propertyNode instanceof SimplePropertyNode ) {
651                SimpleContent content = ( (MappedSimplePropertyType) ( propertyNode.getProperty() ) ).getContent();
652                if ( !( content instanceof MappingField ) ) {
653                    String msg = "Virtual properties are currently ignored in WhereBuilder#appendPropertyPathAsSQL(StatementBuffer,PropertyPath).";
654                    LOG.logError( msg );
655                    throw new RuntimeException( msg );
656                }
657                mappingField = (MappingField) content;
658            } else if ( propertyNode instanceof GeometryPropertyNode ) {
659                mappingField = ( (MappedGeometryPropertyType) propertyNode.getProperty() ).getMappingField();
660            } else {
661                String msg = "Internal error in WhereBuilder: unhandled PropertyNode type: '"
662                             + propertyNode.getClass().getName() + "'.";
663                LOG.logError( msg );
664                throw new RuntimeException( msg );
665            }
666            return mappingField;
667        }
668    
669        /**
670         * Appends an SQL fragment for the given object to the given sql statement.
671         * 
672         * @param query
673         * @param propertyName
674         */
675        protected void appendPropertyNameAsSQL( StringBuffer query, PropertyName propertyName ) {
676    
677            MappingField mappingField = getPropertyNameMapping( propertyName );
678            // with ArcSDE because of versioning not applicable
679            // query.append( mappingField.getTable() );
680            // query.append( '.' );
681            query.append( mappingField.getField() );
682        }
683    
684        /**
685         * Appends an SQL fragment for the given object to the given sql statement.
686         * 
687         * @param query
688         * @param expression
689         * @param targetSqlType
690         */
691        protected void appendArithmeticExpressionAsSQL( StringBuffer query, ArithmeticExpression expression,
692                                                        int targetSqlType ) {
693            query.append( '(' );
694            appendExpressionAsSQL( query, expression.getFirstExpression(), targetSqlType );
695            switch ( expression.getExpressionId() ) {
696            case ExpressionDefines.ADD: {
697                query.append( '+' );
698                break;
699            }
700            case ExpressionDefines.SUB: {
701                query.append( '-' );
702                break;
703            }
704            case ExpressionDefines.MUL: {
705                query.append( '*' );
706                break;
707            }
708            case ExpressionDefines.DIV: {
709                query.append( '/' );
710                break;
711            }
712            }
713            appendExpressionAsSQL( query, expression.getSecondExpression(), targetSqlType );
714            query.append( ')' );
715        }
716    
717        /**
718         * Appends an SQL fragment for the given object to the given sql statement.
719         * 
720         * @param query
721         * @param function
722         * @param targetSqlType
723         */
724        protected void appendFunctionAsSQL( StringBuffer query, Function function, int targetSqlType ) {
725            query.append( function.getName() );
726            query.append( " (" );
727            List list = function.getArguments();
728            for ( int i = 0; i < list.size(); i++ ) {
729                Expression expression = (Expression) list.get( i );
730                appendExpressionAsSQL( query, expression, targetSqlType );
731                if ( i != list.size() - 1 )
732                    query.append( ", " );
733            }
734            query.append( ")" );
735        }
736    
737        /**
738         * Appends an SQL fragment for the given object to the given sql statement.
739         * 
740         * @param query
741         * @param operation
742         */
743        protected void appendLogicalOperationAsSQL( StringBuffer query, LogicalOperation operation ) {
744            List argumentList = operation.getArguments();
745            switch ( operation.getOperatorId() ) {
746            case OperationDefines.AND: {
747                for ( int i = 0; i < argumentList.size(); i++ ) {
748                    Operation argument = (Operation) argumentList.get( i );
749                    query.append( '(' );
750                    appendOperationAsSQL( query, argument );
751                    query.append( ')' );
752                    if ( i != argumentList.size() - 1 )
753                        query.append( " AND " );
754                }
755                break;
756            }
757            case OperationDefines.OR: {
758                for ( int i = 0; i < argumentList.size(); i++ ) {
759                    Operation argument = (Operation) argumentList.get( i );
760                    query.append( '(' );
761                    appendOperationAsSQL( query, argument );
762                    query.append( ')' );
763                    if ( i != argumentList.size() - 1 )
764                        query.append( " OR " );
765                }
766                break;
767            }
768            case OperationDefines.NOT: {
769                Operation argument = (Operation) argumentList.get( 0 );
770                query.append( "NOT (" );
771                appendOperationAsSQL( query, argument );
772                query.append( ')' );
773                break;
774            }
775            }
776        }
777    
778        /**
779         * Appends an SQL fragment for the given object to the given sql statement.
780         * 
781         * TODO Handle compound primary keys correctly.
782         * 
783         * @param query
784         * @param filter
785         */
786        protected void appendFeatureFilterAsSQL( StringBuffer query, FeatureFilter filter ) {
787            ArrayList list = filter.getFeatureIds();
788            MappingField mapping = rootFeatureType.getGMLId().getIdFields()[0];
789            String quote = "";
790            switch ( mapping.getType() ) {
791            case java.sql.Types.DECIMAL:
792            case java.sql.Types.DOUBLE:
793            case java.sql.Types.FLOAT:
794            case java.sql.Types.INTEGER:
795            case java.sql.Types.NUMERIC:
796            case java.sql.Types.REAL:
797            case java.sql.Types.SMALLINT:
798            case java.sql.Types.TINYINT:
799                break;
800            default:
801                quote = "'";
802                break;
803            }
804            query.append( ' ' );
805            query.append( mapping.getField() );
806            try {
807                for ( int i = 0; i < list.size(); i++ ) {
808                    if ( 0 == i )
809                        query.append( " IN (" + quote );
810                    else
811                        query.append( quote + "," + quote );
812                    String fid = ( (org.deegree.model.filterencoding.FeatureId) list.get( i ) ).getValue();
813                    Object fidValue = org.deegree.io.datastore.FeatureId.removeFIDPrefix( fid, rootFeatureType.getGMLId() );
814                    query.append( fidValue.toString() );
815                }
816            } catch ( Exception e ) {
817                LOG.logError( "Error converting feature id", e );
818            }
819            query.append( quote + ")" );
820        }
821    
822        /**
823         * Generates an SQL-fragment for the given object.
824         * 
825         * @param filter
826         * @param layers
827         * @return
828         * 
829         * @throws DatastoreException
830         */
831        protected SeFilter[] buildSpatialFilter( ComplexFilter filter, List layers )
832                                throws DatastoreException {
833    
834            SpatialOperation[] spatialOps = FilterTools.extractSpatialFilter( filter );
835            if ( null == spatialOps || 0 == spatialOps.length )
836                return null;
837    
838            SeFilter[] spatialFilter = new SeFilter[spatialOps.length];
839    
840            for ( int i = 0; i < spatialOps.length; i++ ) {
841                try {
842                    MappingField mappingField = getPropertyNameMapping( spatialOps[i].getPropertyName() );
843                    String filterTable = mappingField.getTable();
844                    String filterColumn = mappingField.getField();
845    
846                    SeCoordinateReference coordRef = null;
847                    String[] splitted = filterTable.toUpperCase().split( "\\." );
848                    String tmp = splitted[splitted.length - 1];
849                    for ( int k = 0; k < layers.size(); k++ ) {
850                        SeLayer layer = (SeLayer) layers.get( k );
851                        splitted = layer.getName().toUpperCase().split( "\\." );
852                        if ( splitted[splitted.length - 1].equals( tmp ) ) {
853                            coordRef = layer.getCoordRef();
854                            break;
855                        }
856                    }
857                    if ( null == coordRef ) {
858                        coordRef = new SeCoordinateReference();
859                    }
860    
861                    int filterMethod = -1;
862                    boolean filterTruth = true;
863                    switch ( spatialOps[i].getOperatorId() ) {
864                    case OperationDefines.CROSSES: {
865                        filterMethod = SeFilter.METHOD_LCROSS;
866                        break;
867                    }
868                    case OperationDefines.EQUALS: {
869                        filterMethod = SeFilter.METHOD_IDENTICAL;
870                        break;
871                    }
872                    case OperationDefines.WITHIN: {
873                        filterMethod = SeFilter.METHOD_SC_NO_ET;
874                        break;
875                    }
876                    case OperationDefines.OVERLAPS: {
877                        filterMethod = SeFilter.METHOD_ENVP;
878                        break;
879                    }
880                    case OperationDefines.TOUCHES: {
881                        filterMethod = SeFilter.METHOD_ET_OR_AI;
882                        break;
883                    }
884                    case OperationDefines.DISJOINT: {
885                        filterMethod = SeFilter.METHOD_SC_NO_ET;
886                        filterTruth = false;
887                        break;
888                    }
889                    case OperationDefines.INTERSECTS: {
890                        filterMethod = SeFilter.METHOD_AI;
891                        break;
892                    }
893                    case OperationDefines.CONTAINS: {
894                        filterMethod = SeFilter.METHOD_AI_OR_ET;
895                        break;
896                    }
897                    case OperationDefines.BBOX: {
898                        filterMethod = SeFilter.METHOD_ENVP;
899                        break;
900                    }
901                    case OperationDefines.DWITHIN:
902                    case OperationDefines.BEYOND:
903                    default: {
904                        continue;
905                    }
906                    }
907                    SeShape filterGeom = SDEAdapter.export( spatialOps[i].getGeometry(), coordRef );
908                    spatialFilter[i] = new SeShapeFilter( filterTable, filterColumn, filterGeom, filterMethod, filterTruth );
909                } catch ( Exception e ) {
910                    e.printStackTrace();
911                    throw new DatastoreException( "Error creating spatial filter", e );
912                }
913            }
914            return spatialFilter;
915        }
916    }