001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/quadtree/DBNode.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     ---------------------------------------------------------------------------*/
043    package org.deegree.io.quadtree;
044    
045    import java.security.InvalidParameterException;
046    import java.sql.Connection;
047    import java.sql.PreparedStatement;
048    import java.sql.ResultSet;
049    import java.sql.SQLException;
050    import java.sql.Statement;
051    import java.util.ArrayList;
052    import java.util.List;
053    
054    import org.deegree.framework.log.ILogger;
055    import org.deegree.framework.log.LoggerFactory;
056    import org.deegree.io.DBConnectionPool;
057    import org.deegree.io.DBPoolException;
058    import org.deegree.io.JDBCConnection;
059    import org.deegree.io.quadtree.DBQuadtree.SupportedVersions;
060    import org.deegree.model.spatialschema.Envelope;
061    import org.deegree.model.spatialschema.GeometryFactory;
062    import org.deegree.model.spatialschema.Position;
063    
064    /**
065     * Represents a node of a {@link DBQuadtree}. Nodes contain items which have a spatial extent corresponding to the
066     * node's position in the quadtree.
067     * 
068     * 
069     * @version $Revision: 9342 $
070     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
071     * @author last edited by: $Author: apoth $
072     * 
073     * @version 1.0. $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $
074     * 
075     * @since 2.0
076     */
077    class DBNode<T> implements Node<T> {
078    
079        private static ILogger LOG = LoggerFactory.getLogger( DBNode.class );
080    
081        private String id = null;
082    
083        private int level;
084    
085        private String[] fk_subnode = new String[4];
086    
087        private Envelope envelope = null;
088    
089        private JDBCConnection jdbc = null;
090    
091        private DBQuadtree<T> qt = null;
092    
093        private String indexTable = null;
094    
095        private SupportedVersions version;
096    
097        private String indexItemTable;
098    
099        /**
100         * A constructor which reads the envelope from the database. Using an old Quadtree layout.
101         * 
102         * @param id
103         * @param qt
104         * @param indexTable
105         * @param jdbc
106         * @param level
107         * @throws IndexException
108         *             if the node with given id could not be read from the db.
109         */
110        // public DBNode( String id, DBQuadtree<T> qt, String indexTable, JDBCConnection jdbc, int level )
111        // throws IndexException {
112        // this( id, null, qt, indexTable, jdbc, level, SupportedVersions.ONE );
113        // }
114        /**
115         * 
116         * @param id
117         * @param env
118         * @param qt
119         * @param indexTable
120         * @param jdbc
121         * @param level
122         * @param version
123         *            of the quadtree layout to use
124         * @throws IndexException
125         *             if the node with given id could not be read from the db.
126         */
127        public DBNode( String id, Envelope env, DBQuadtree<T> qt, String indexTable, JDBCConnection jdbc, int level,
128                       SupportedVersions version ) throws IndexException {
129            this.id = id;
130            this.envelope = env;
131            if ( jdbc == null ) {
132                throw new InvalidParameterException( "The JDBCConnection reference parameter 'jdbc' may not be null." );
133            }
134            this.jdbc = jdbc;
135    
136            if ( qt == null ) {
137                throw new InvalidParameterException( "The quadtree reference parameter 'qt' may not be null." );
138            }
139            this.qt = qt;
140    
141            if ( level < 1 ) {
142                level = 1;
143            }
144            this.level = level;
145            if ( indexTable == null || "".equals( indexTable.trim() ) ) {
146                throw new InvalidParameterException( "The Table reference String 'indexTable' may neither be null nor an empty string." );
147            }
148            this.indexTable = indexTable.trim();
149            this.indexItemTable = this.indexTable + "_ITEM ";
150            this.version = version;
151            if ( !loadNodeFromDB() ) {
152                addNodeToDB();
153            }
154            for ( int i = 0; i < fk_subnode.length; ++i ) {
155                if ( fk_subnode[i] == null || "null".equalsIgnoreCase( fk_subnode[i].trim() ) ) {
156                    fk_subnode[i] = "";
157                }
158            }
159            qt.addToCache( this );
160        }
161    
162        /**
163         * A constructor which reads the envelope from the database.
164         * 
165         * @param id
166         * @param qt
167         * @param indexTable
168         * @param jdbc
169         * @param level
170         * @param version
171         *            of the quadtree layout to use.
172         * @throws IndexException
173         *             if the node with given id could not be read from the db.
174         */
175        public DBNode( String id, DBQuadtree<T> qt, String indexTable, JDBCConnection jdbc, int level,
176                       SupportedVersions version ) throws IndexException {
177            this( id, null, qt, indexTable, jdbc, level, version );
178        }
179    
180        public boolean insert( T itemKey, Envelope itemEnv )
181                                                            throws IndexException {
182            if ( version == SupportedVersions.ONE ) {
183                return insertWithLayoutOne( itemKey, itemEnv );
184            } else if ( version == SupportedVersions.TWO ) {
185                return insertWithLayoutTwo( itemKey, itemEnv );
186            }
187            return false;
188        }
189    
190        public void deleteRange( Envelope envelope ) {
191            if ( level == qt.getDepth() ) {
192                // TODO delete a range from the bottomlevel
193            } else {
194                // TODO delete a range smaller then the depth
195            }
196        }
197    
198        /**
199         * @throws UnsupportedOperationException
200         *             if the version of this quadtree(node) is not 2.0.0 or higher.
201         */
202        public boolean delete( T itemKey, Envelope itemEnv )
203                                                            throws IndexException {
204            if ( version != SupportedVersions.TWO ) {
205                String msg = "Deleting of items is only supported for the quadtree structure with version '2.0.0' or higher";
206                LOG.logError( msg );
207                throw new UnsupportedOperationException( msg );
208            }
209            Connection dbConnection = null;
210            DBConnectionPool pool = null;
211            pool = DBConnectionPool.getInstance();
212            try {
213                dbConnection = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
214            } catch ( DBPoolException e ) {
215                throw new IndexException( e );
216            }
217            boolean result = deleteWithLayoutTwo( itemKey, itemEnv, null, dbConnection );
218    
219            try {
220                pool.releaseConnection( dbConnection, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
221            } catch ( DBPoolException e ) {
222                LOG.logError( "Could not release the db Connection after deletion in the quadtree because: " + e.getMessage() );
223            }
224            return result;
225    
226        }
227    
228        public boolean update( T itemKey, Envelope newBBox ) {
229            // nottin yet
230            return true;
231        }
232    
233        public List<T> query( Envelope searchEnv, List<T> visitor, int currentLevel )
234                                                                                     throws IndexException {
235            if ( version == SupportedVersions.ONE ) {
236                LOG.logDebug( "Performing query with layout 1" );
237                queryWithLayoutOne( searchEnv, visitor, currentLevel );
238            } else if ( version == SupportedVersions.TWO ) {
239                LOG.logDebug( "Performing query with layout 2" );
240                queryWithLayoutTwo( searchEnv, visitor );
241            }
242            return visitor;
243        }
244    
245        public String getId() {
246            return id;
247        }
248    
249        /**
250         * @return the envelope (bbox) of this dbNode.
251         */
252        public Envelope getEnvelope() {
253            return envelope;
254        }
255    
256        /**
257         * creates a new node with current ID and envelope
258         * 
259         * @throws IndexException
260         */
261        private void addNodeToDB()
262                                  throws IndexException {
263            Connection con = null;
264            DBConnectionPool pool = null;
265            try {
266                pool = DBConnectionPool.getInstance();
267                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
268    
269                StringBuilder sb = new StringBuilder( 100 );
270                sb.append( "INSERT INTO " ).append( indexTable );
271                sb.append( " ( ID, MINX, MINY, MAXX , MAXY ) " );
272                sb.append( "VALUES ( ?, ?, ?, ?, ? ) " );
273                PreparedStatement stmt = con.prepareStatement( sb.toString() );
274                stmt.setString( 1, id );
275                stmt.setFloat( 2, (float) envelope.getMin().getX() );
276                stmt.setFloat( 3, (float) envelope.getMin().getY() );
277                stmt.setFloat( 4, (float) envelope.getMax().getX() );
278                stmt.setFloat( 5, (float) envelope.getMax().getY() );
279                stmt.execute();
280                stmt.close();
281            } catch ( Exception e ) {
282                LOG.logError( e.getMessage(), e );
283                throw new IndexException( "could not create node definition at database", e );
284            } finally {
285                try {
286                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
287                } catch ( Exception e1 ) {
288                    e1.printStackTrace();
289                }
290            }
291        }
292    
293        /**
294         * assignes an item to a node by creating a new row in the JT_QTNODE_ITEM table
295         * 
296         * @param Item
297         */
298        private void assignItem( T itemKey )
299                                            throws IndexException {
300            Connection con = null;
301            DBConnectionPool pool = null;
302            try {
303                pool = DBConnectionPool.getInstance();
304                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
305    
306                StringBuilder sb = new StringBuilder( 100 );
307                sb.append( "INSERT INTO " ).append( indexItemTable );
308                sb.append( "( FK_QTNODE, FK_ITEM ) " ).append( "VALUES ( ?, ? ) " );
309                PreparedStatement stmt = con.prepareStatement( sb.toString() );
310                stmt.setString( 1, id );
311                if ( itemKey instanceof Integer ) {
312                    stmt.setInt( 2, ( (Integer) itemKey ).intValue() );
313                } else {
314                    stmt.setString( 2, itemKey.toString() );
315                }
316                stmt.execute();
317                stmt.close();
318            } catch ( Exception e ) {
319                LOG.logError( e.getMessage(), e );
320                throw new IndexException( "could not create node definition at database", e );
321            } finally {
322                try {
323                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
324                } catch ( Exception e1 ) {
325                    e1.printStackTrace();
326                }
327            }
328        }
329    
330        /**
331         * This method inserts the given item into the quadtree. The difference between layout ONE and TWO is, that in
332         * version TWO all nodes which lie inside the item's bbox possess a reference to the item, allthough the idx_table
333         * will be larger, the retrieval of multiple items inside a requested bbox will be a lot faster.
334         * 
335         * @param itemKey
336         *            to be inserted
337         * @param itemEnv
338         *            bbox of the item to be inserted.
339         * @return true if the item was successfully inserted into the quadtree false otherwise.
340         * @throws IndexException
341         *             if the item could not be inserted because of an <code>db</code> or <code>sql</code> error.
342         */
343        private boolean insertWithLayoutTwo( T itemKey, Envelope itemEnv )
344                                                                          throws IndexException {
345            if ( level != qt.getDepth() ) {
346                if ( !envelope.intersects( itemEnv ) ) {
347                    String msg = "The item's envelope: " + itemEnv
348                                 + " does not intersect with the quadtrees' boundinbox envelope: "
349                                 + envelope;
350                    LOG.logError( msg );
351                    throw new IndexException( msg );
352                }
353                // split the envelope of this node into four equal sized quarters
354                Envelope[] envs = split();
355                boolean needsUpdate = false;
356                boolean nodeInserted = false;
357                for ( int i = 0; i < envs.length; i++ ) {
358                    if ( envs[i].intersects( itemEnv ) ) {
359                        // check which subnodes are intersected by the
360                        // items envelope; only these nodes are considered for futher processing
361                        if ( "".equals( fk_subnode[i].trim() ) ) {
362                            needsUpdate = true;
363                            fk_subnode[i] = id + '_' + i;
364                        }
365                        DBNode<T> node = qt.getFromCache( fk_subnode[i] );
366                        if ( node == null ) {
367                            node = new DBNode<T>( fk_subnode[i], envs[i], qt, indexTable, jdbc, level + 1, version );
368                        }
369                        nodeInserted = node.insertWithLayoutTwo( itemKey, itemEnv );
370                    }
371                }
372                // if any child node inserted the item, the parent must know about it too, this enhances the speed of area
373                // querying.
374                if ( nodeInserted ) {
375                    assignItem( itemKey );
376                }
377                if ( needsUpdate ) {
378                    updateNodeInDB();
379                }
380                return nodeInserted;
381            }
382            assignItem( itemKey );
383            return true;
384        }
385    
386        /**
387         * @param itemEnv
388         *            the items envelope
389         * @return true if this nodes envelope lies completely inside the items envelope.
390         */
391        private boolean liesWithIn( Envelope itemEnv ) {
392            Position minThis = envelope.getMin();
393            Position maxThis = envelope.getMax();
394            Position minThat = itemEnv.getMin();
395            Position maxThat = itemEnv.getMax();
396            return ( minThis.getX() >= minThat.getX() && maxThis.getX() <= maxThat.getX()
397                     && minThis.getY() > minThat.getY() && maxThis.getY() < maxThat.getY() );
398    
399        }
400    
401        private boolean insertWithLayoutOne( T itemKey, Envelope itemEnv )
402                                                                          throws IndexException {
403            if ( level != qt.getDepth() ) {
404                if ( !envelope.intersects( itemEnv ) ) {
405                    String msg = "The item's envelope: " + itemEnv
406                                 + " does not intersect with the quadtrees' boundinbox envelope: "
407                                 + envelope;
408                    LOG.logError( msg );
409                    throw new IndexException( msg );
410                }
411                // split the envelope of this node into four equal sized quarters
412                Envelope[] envs = split();
413                boolean needsUpdate = false;
414                int numberOfInsertedSons = 0;
415                for ( int i = 0; i < envs.length; i++ ) {
416                    if ( envs[i].intersects( itemEnv ) ) {
417                        numberOfInsertedSons++;
418                        // check which subnodes are intersected by the
419                        // items envelope; only these nodes are considered for futher processing
420                        if ( "".equals( fk_subnode[i].trim() ) ) {
421                            needsUpdate = true;
422                            fk_subnode[i] = id + '_' + i;
423                        }
424                        DBNode<T> node = qt.getFromCache( fk_subnode[i] );
425                        if ( node == null ) {
426                            node = new DBNode<T>( fk_subnode[i], envs[i], qt, indexTable, jdbc, level + 1, version );
427                        }
428                        node.insertWithLayoutOne( itemKey, itemEnv );
429                    }
430                }
431                if ( numberOfInsertedSons == 4 ) {
432                    assignItem( itemKey );
433                }
434                if ( needsUpdate ) {
435                    updateNodeInDB();
436                }
437            } else {
438                assignItem( itemKey );
439            }
440            return true;
441        }
442    
443        /**
444         * The enhancement of the second layout is, that every node knows about the items inserted in it's sons. This
445         * information can be used if the bbox of this node lies totally within the requested area, all items of the sons of
446         * this node are added automatically.
447         * 
448         * @param searchEnv
449         *            the area to get all items for.
450         * @param visitor
451         *            the list inwhich the items keys will be added.
452         * @param currentLevel
453         *            of the tree
454         * @throws IndexException
455         *             if a connection to the db failed.
456         */
457        private void queryWithLayoutTwo( Envelope searchEnv, List<T> visitor )
458                                                                              throws IndexException {
459            if ( liesWithIn( searchEnv ) || level == qt.getDepth() ) {
460                getAssignedItems( visitor );
461            } else {
462                Envelope[] envs = split();
463                for ( int i = 0; i < envs.length; i++ ) {
464                    if ( !"".equals( fk_subnode[i] ) && envs[i].intersects( searchEnv ) ) {
465                        // check which subnodes are intersected by the
466                        // items envelope; just this nodes
467                        // are considered for futher processing
468                        DBNode<T> node = new DBNode<T>( fk_subnode[i], envs[i], qt, indexTable, jdbc, level + 1, version );
469                        node.queryWithLayoutTwo( searchEnv, visitor );
470                    }
471                }
472            }
473    
474        }
475    
476        private void queryWithLayoutOne( Envelope searchEnv, List<T> visitor, int level )
477                                                                                         throws IndexException {
478            /*
479             * if ( level == qt.getDepth() || (searchEnv.getWidth() > envelope.getWidth() || searchEnv.getHeight() >
480             * envelope.getHeight()) ) { addAssignedItems( visitor ); } else {
481             */
482            getAssignedItems( visitor );
483            if ( level != qt.getDepth() ) {
484                Envelope[] envs = split();
485                for ( int i = 0; i < envs.length; i++ ) {
486                    if ( !"".equals( fk_subnode[i] ) && envs[i].intersects( searchEnv ) ) {
487                        // check which subnodes are intersected by the
488                        // items envelope; just this nodes
489                        // are considered for futher processing
490                        DBNode<T> node = new DBNode<T>( fk_subnode[i], envs[i], qt, indexTable, jdbc, level + 1, version );
491                        node.queryWithLayoutOne( searchEnv, visitor, level + 1 );
492                    }
493                }
494            }
495        }
496    
497        /**
498         * The delete method which can handle the layout with version two.
499         * 
500         * @param itemKey
501         *            the key to be deleted.
502         * @param itemEnv
503         *            the bbox of the item.
504         * @param parent
505         *            the parent node of this qt-node or <code>null</code> if this is the root node.
506         * @throws IndexException
507         *             if somehow the connection to the db is lost or an error occurred while executing the deletion.
508         */
509        private boolean deleteWithLayoutTwo( T itemKey, Envelope itemEnv, DBNode<T> parent, Connection dbConnection )
510                                                                                                                     throws IndexException {
511            List<T> items = new ArrayList<T>();
512            // first get all items assigned to this node, and remove the fitting item from the idx-item join table.
513            getAssignedItems( items );
514            if ( items.contains( itemKey ) ) {
515                if ( deleteItemFromDB( itemKey, dbConnection ) ) {
516                    items.remove( itemKey );
517                }
518            }
519            if ( level != qt.getDepth() ) {
520                Envelope[] envs = split();
521                for ( int i = 0; i < fk_subnode.length; ++i ) {
522                    if ( !"".equals( fk_subnode[i] ) && envs[i].intersects( itemEnv ) ) {
523                        DBNode<T> node = new DBNode<T>( fk_subnode[i], envs[i], qt, indexTable, jdbc, level + 1, version );
524                        node.deleteWithLayoutTwo( itemKey, itemEnv, this, dbConnection );
525                    }
526                }
527            }
528            // If this is not the root of the quadtree, delete this node from the parent if it is a leaf and has no items.
529            if ( parent != null ) {
530                if ( items.isEmpty() && !hasSons() ) {
531                    if ( deletNodeFromDB( dbConnection ) ) {
532                        if ( parent.deleteSon( id, dbConnection ) ) {
533                            qt.addToCache( parent );
534                            qt.removeFromCache( this );
535                        }
536                    }
537                }
538            }
539            return true;
540        }
541    
542        /**
543         * @param dbConnection
544         *            a live connection to the database
545         * @return true if the node has been successfully removed from the database.
546         */
547        private boolean deletNodeFromDB( Connection dbConnection ) {
548            if ( hasSons() ) {
549                LOG.logError( "Trying to delete a node (with id: '" + id + "') which still has sons, this may not happen!" );
550                return false;
551            }
552            StringBuilder deleteStatement = new StringBuilder( 400 );
553            deleteStatement.append( "DELETE FROM " ).append( indexTable );
554            deleteStatement.append( " WHERE ID = '" ).append( id ).append( "'" );
555            LOG.logDebug( "Trying to delete dbnode with id='" + id
556                          + "'. The sql statement is as follows:\n"
557                          + deleteStatement.toString() );
558            Statement stmt = null;
559            ResultSet rs = null;
560            try {
561                stmt = dbConnection.createStatement();
562                rs = stmt.executeQuery( deleteStatement.toString() );
563            } catch ( SQLException e ) {
564                LOG.logDebug( "Could not delete the dbNode with id='" + id + "' because: " + e.getMessage() );
565                return false;
566            } finally {
567                try {
568                    if ( rs != null ) {
569                        rs.close();
570                    }
571                    if ( stmt != null ) {
572                        stmt.close();
573                    }
574                } catch ( SQLException e ) {
575                    LOG.logDebug( "An error occurred while trying to close statement and/or resultset because: " + e.getMessage() );
576                }
577    
578            }
579            return true;
580        }
581    
582        /**
583         * @return true if this node has a son (e.g. one of the fk_subnodes is not empty ).
584         */
585        private boolean hasSons() {
586            return !( "".equals( fk_subnode[0] ) && "".equals( fk_subnode[1] ) && "".equals( fk_subnode[2] ) && "".equals( fk_subnode[3] ) );
587    
588        }
589    
590        /**
591         * Deletes the specifies item from the quadtree, e.g. removing it from the idx_table_item join table, where the
592         * FK_QTNODE equals this.id and the FK_ITEM = itemKey.
593         * 
594         * @param itemKey
595         *            to be deleted
596         * @param dbConnection
597         *            used to execute the query.
598         * @return true if the item was deleted from the database, false otherwhise.
599         */
600        private boolean deleteItemFromDB( T itemKey, Connection dbConnection ) {
601            if ( itemKey == null ) {
602                LOG.logDebug( "Trying to delete an itemkey which is null, this may not be (current node id='" + id + "')" );
603                return false;
604            }
605            StringBuilder delItemStatement = new StringBuilder( 400 );
606            delItemStatement.append( "DELETE FROM " ).append( indexItemTable );
607            delItemStatement.append( " WHERE FK_QTNODE = '" ).append( id ).append( "'" );
608            delItemStatement.append( " AND FK_ITEM = " );
609            if ( itemKey instanceof String ) {
610                delItemStatement.append( "'" );
611            }
612            delItemStatement.append( itemKey );
613            if ( itemKey instanceof String ) {
614                delItemStatement.append( "'" );
615            }
616            LOG.logDebug( "Trying to delete item with key='" + itemKey
617                          + "' from node with id='"
618                          + id
619                          + "'. The sql statement is as follows:\n"
620                          + delItemStatement.toString() );
621            Statement stmt = null;
622            ResultSet rs = null;
623            try {
624                stmt = dbConnection.createStatement();
625                rs = stmt.executeQuery( delItemStatement.toString() );
626            } catch ( SQLException e ) {
627                LOG.logDebug( "Could not delete the item with key='" + itemKey
628                              + "' from node with id='"
629                              + id
630                              + "' because: "
631                              + e.getMessage() );
632                return false;
633            } finally {
634                try {
635                    if ( rs != null ) {
636                        rs.close();
637                    }
638                    if ( stmt != null ) {
639                        stmt.close();
640                    }
641                } catch ( SQLException e ) {
642                    LOG.logDebug( "An error occurred while trying to close statement and/or resultset because: " + e.getMessage() );
643                }
644    
645            }
646    
647            return true;
648    
649        }
650    
651        /**
652         * Deletes a son from the database, e.g. perform an update to null on the idx_table. And if successfull also sets
653         * the fk_subnode to an empty string.
654         * 
655         * @param sonsID
656         *            which must be set to null.
657         * @param dbConnection
658         *            which will be used to perform the query.
659         * @return true if the son was deleted.
660         */
661        private boolean deleteSon( String sonsID, Connection dbConnection ) {
662            if ( sonsID == null || "".equals( sonsID.trim() ) ) {
663                LOG.logDebug( "Trying to delete a son with an id which is null, this may not be (parent node id='" + id
664                              + "')" );
665                return false;
666            }
667            for ( int i = 0; i < fk_subnode.length; ++i ) {
668                if ( fk_subnode[i].trim().equals( sonsID.trim() ) ) {
669                    StringBuilder delSonStatement = new StringBuilder( 400 );
670                    delSonStatement.append( "UPDATE " ).append( indexTable );
671                    delSonStatement.append( " SET FK_SUBNODE" ).append( ( i + 1 ) ).append( " = 'null'" );
672                    delSonStatement.append( " WHERE ID = '" ).append( id ).append( "'" );
673    
674                    LOG.logDebug( "Trying to delete son with id='" + sonsID
675                                  + "' from (parent) node with id='"
676                                  + this.id
677                                  + "'. The sql statement is as follows:\n"
678                                  + delSonStatement.toString() );
679                    Statement stmt = null;
680                    ResultSet rs = null;
681                    try {
682                        stmt = dbConnection.createStatement();
683                        rs = stmt.executeQuery( delSonStatement.toString() );
684                    } catch ( SQLException e ) {
685                        LOG.logDebug( "Could not delete son with id='" + sonsID
686                                      + "' from (parent) node with id='"
687                                      + this.id
688                                      + "' because: "
689                                      + e.getMessage() );
690                        return false;
691                    } finally {
692                        try {
693                            if ( rs != null ) {
694                                rs.close();
695                            }
696                            if ( stmt != null ) {
697                                stmt.close();
698                            }
699                        } catch ( SQLException e ) {
700                            LOG.logDebug( "An error occurred while trying to close statement and/or resultset because: " + e.getMessage() );
701                        }
702    
703                    }
704                    fk_subnode[i] = "";
705                    return true;
706                }
707    
708            }
709    
710            LOG.logDebug( "It seems this (parent) node with id='" + id
711                          + "' has no son with id='"
712                          + sonsID
713                          + "', all sons of this node  are:\n "
714                          + "- fk_subnode[0]='"
715                          + fk_subnode[0]
716                          + "'\n "
717                          + "- fk_subnode[1]='"
718                          + fk_subnode[1]
719                          + "'\n "
720                          + "- fk_subnode[2]='"
721                          + fk_subnode[2]
722                          + "'\n "
723                          + "- fk_subnode[3]='"
724                          + fk_subnode[3]
725                          + "'" );
726            return false;
727        }
728    
729        /**
730         * load all Node parameters from the database.
731         * 
732         * @return true if a node with current ID is already available from the database and the parameters have been read
733         *         successfully.
734         * 
735         */
736        private boolean loadNodeFromDB()
737                                        throws IndexException {
738            Connection con = null;
739            DBConnectionPool pool = null;
740            boolean available = true;
741            try {
742                pool = DBConnectionPool.getInstance();
743                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
744    
745                StringBuilder sb = new StringBuilder( 100 );
746                sb.append( "Select * from " ).append( indexTable );
747                sb.append( " where ID = '" ).append( id ).append( "'" );
748    
749                Statement stmt = con.createStatement();
750                ResultSet rs = stmt.executeQuery( sb.toString() );
751                if ( rs.next() ) {
752                    double minx = rs.getFloat( "MINX" );
753                    double miny = rs.getFloat( "MINY" );
754                    double maxx = rs.getFloat( "MAXX" );
755                    double maxy = rs.getFloat( "MAXY" );
756                    envelope = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, null );
757                    fk_subnode[0] = rs.getString( "FK_SUBNODE1" );
758                    fk_subnode[1] = rs.getString( "FK_SUBNODE2" );
759                    fk_subnode[2] = rs.getString( "FK_SUBNODE3" );
760                    fk_subnode[3] = rs.getString( "FK_SUBNODE4" );
761                } else {
762                    available = false;
763                }
764                rs.close();
765                stmt.close();
766            } catch ( Exception e ) {
767                LOG.logError( e.getMessage(), e );
768                throw new IndexException( "could not load node definition from database", e );
769            } finally {
770                try {
771                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
772                } catch ( Exception e1 ) {
773                    e1.printStackTrace();
774                }
775            }
776            return available;
777        }
778    
779        /**
780         * updates the database representation of the current node
781         * 
782         * @throws IndexException
783         */
784        private void updateNodeInDB()
785                                     throws IndexException {
786            Connection con = null;
787            DBConnectionPool pool = null;
788            try {
789                pool = DBConnectionPool.getInstance();
790                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
791    
792                StringBuilder sb = new StringBuilder( 250 );
793                sb.append( "UPDATE " ).append( indexTable ).append( " set " );
794                boolean submit = false;
795                for ( int i = 0; i < fk_subnode.length; i++ ) {
796                    if ( !"".equals( fk_subnode[i] ) ) {
797                        sb.append( " FK_SUBNODE" ).append( i + 1 ).append( "='" );
798                        sb.append( fk_subnode[i] ).append( "' ," );
799                        submit = true;
800                    }
801                }
802                if ( submit ) {
803                    // just execute update if at least one sub node != null
804                    sb = new StringBuilder( sb.substring( 0, sb.length() - 1 ) );
805                    sb.append( " where ID = '" ).append( id ).append( "'" );
806                    Statement stmt = con.createStatement();
807                    stmt.execute( sb.toString() );
808                    stmt.close();
809                }
810            } catch ( Exception e ) {
811                LOG.logError( e.getMessage(), e );
812                throw new IndexException( "could not update node definition at database " + "for node: " + id, e );
813            } finally {
814                try {
815                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
816                } catch ( Exception e1 ) {
817                    e1.printStackTrace();
818                }
819            }
820            qt.addToCache( this );
821        }
822    
823        /**
824         * Get the item (IDs) assigned to this node and stores them in the given list
825         * 
826         * @param visitor
827         * @throws IndexException
828         */
829        private void getAssignedItems( List<T> visitor )
830                                                        throws IndexException {
831    
832            Connection con = null;
833            DBConnectionPool pool = null;
834            try {
835                pool = DBConnectionPool.getInstance();
836                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
837    
838                StringBuilder sb = new StringBuilder( 100 );
839                sb.append( "SELECT DISTINCT FK_ITEM from " ).append( indexTable ).append( "_ITEM" );
840                sb.append( " where " ).append( "FK_QTNODE = '" ).append( id ).append( "'" );
841                Statement stmt = con.createStatement();
842                ResultSet rs = stmt.executeQuery( sb.toString() );
843    
844                while ( rs.next() ) {
845                    Object result = rs.getObject( 1 );
846                    if ( result != null ) {
847                        T s = (T) result;
848                        if ( !visitor.contains( s ) ) {
849                            visitor.add( s );
850                        }
851                    } else {
852                        LOG.logDebug( "Found a node (id='" + id + "') with a null value." );
853                    }
854                }
855                stmt.close();
856            } catch ( DBPoolException e ) {
857                throw new IndexException( "Database QuadTree could not acquire sons of a node because: " + e.getMessage() );
858            } catch ( SQLException e ) {
859                throw new IndexException( "Database QuadTree could not acquire sons of a node because: " + e.getMessage() );
860            } finally {
861                try {
862                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
863                } catch ( Exception e1 ) {
864                    e1.printStackTrace();
865                }
866            }
867        }
868    
869        private Envelope[] split() {
870            Envelope[] envs = new Envelope[4];
871            double nW = envelope.getWidth() / 2d;
872            double nH = envelope.getHeight() / 2d;
873    
874            envs[0] = GeometryFactory.createEnvelope( envelope.getMin().getX(),
875                                                      envelope.getMin().getY(),
876                                                      envelope.getMin().getX() + nW,
877                                                      envelope.getMin().getY() + nH,
878                                                      null );
879            envs[1] = GeometryFactory.createEnvelope( envelope.getMin().getX() + nW,
880                                                      envelope.getMin().getY(),
881                                                      envelope.getMin().getX() + ( 2 * nW ),
882                                                      envelope.getMin().getY() + nH,
883                                                      null );
884            envs[2] = GeometryFactory.createEnvelope( envelope.getMin().getX() + nW,
885                                                      envelope.getMin().getY() + nH,
886                                                      envelope.getMin().getX() + ( 2 * nW ),
887                                                      envelope.getMin().getY() + ( 2 * nH ),
888                                                      null );
889            envs[3] = GeometryFactory.createEnvelope( envelope.getMin().getX(),
890                                                      envelope.getMin().getY() + nH,
891                                                      envelope.getMin().getX() + nW,
892                                                      envelope.getMin().getY() + ( 2 * nH ),
893                                                      null );
894    
895            return envs;
896        }
897    
898        // /**
899        // * @param itemKey
900        // * the key of the item to get the bbox for.
901        // * @return the envelope of item with itemKey, currently stored in the quadtree.
902        // */
903        // public Envelope getBoundingBoxForItem( T itemKey ) {
904        // return null;
905        // }
906    
907    }