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