/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.jdbc;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import org.geotools.data.Query;
import org.geotools.factory.Hints;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.jdbc.JoinInfo.JoinPart;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
/**
* Feature reader that wraps multiple feature readers in a join query.
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class JDBCJoiningFeatureReader extends JDBCFeatureReader {
List<JDBCFeatureReader> joinReaders;
SimpleFeatureBuilder joinFeatureBuilder;
public JDBCJoiningFeatureReader(String sql, Connection cx, JDBCFeatureSource featureSource,
SimpleFeatureType featureType, JoinInfo join, Query query)
throws SQLException, IOException {
//super(sql, cx, featureSource, retype(featureType, join), hints);
super(sql, cx, featureSource, featureType, query);
init(cx, featureSource, featureType, join, query);
}
public JDBCJoiningFeatureReader(PreparedStatement st, Connection cx, JDBCFeatureSource featureSource,
SimpleFeatureType featureType, JoinInfo join, Query query)
throws SQLException, IOException {
super(st, cx, featureSource, featureType, query);
init(cx, featureSource, featureType, join, query);
}
void init(Connection cx, JDBCFeatureSource featureSource, SimpleFeatureType featureType,
JoinInfo join, Query query) throws SQLException, IOException {
joinReaders = new ArrayList<JDBCFeatureReader>();
int offset = featureType.getAttributeCount()
+ getPrimaryKeyOffset(featureSource, getPrimaryKey(), featureType);
for (JoinPart part : join.getParts()) {
SimpleFeatureType ft = part.getQueryFeatureType();
JDBCFeatureReader joinReader = new JDBCFeatureReader(rs, cx, offset, featureSource.getDataStore()
.getAbsoluteFeatureSource(ft.getTypeName()), ft, query) {
@Override
protected void finalize() throws Throwable {
// Do nothing.
//
// This override protects the injected result set and connection from being
// closed by the garbage collector, which is unwanted because this is a
// delegate which uses resources that will be closed elsewhere, or so it
// is claimed in the comment in the close() method below. See GEOT-4204.
}
};
joinReaders.add(joinReader);
offset += ft.getAttributeCount()
+ getPrimaryKeyOffset(featureSource, joinReader.getPrimaryKey(), ft);
}
//builder for the final joined feature
joinFeatureBuilder = new SimpleFeatureBuilder(retype(featureType, join));
}
private int getPrimaryKeyOffset(JDBCFeatureSource featureSource, PrimaryKey pk,
SimpleFeatureType featureType) {
// if we are not exposing them, they are all extras
int pkSize = pk.getColumns().size();
if (!featureSource.isExposePrimaryKeyColumns()) {
return pkSize;
}
// otherwise, we have to check if they are requested or not, as we are going to
// have them anyways as part of the sql query, but not necessarily in the requested ft
int requestedPkColumns = 0;
for (AttributeDescriptor ad : featureType.getAttributeDescriptors()) {
if (ad.getUserData().get(JDBCDataStore.JDBC_PRIMARY_KEY_COLUMN) == Boolean.TRUE) {
requestedPkColumns++;
}
}
return pkSize - requestedPkColumns;
}
@Override
public boolean hasNext() throws IOException {
boolean next = super.hasNext();
for (JDBCFeatureReader r : joinReaders) {
r.setNext(next);
}
return next;
}
@Override
public SimpleFeature next() throws IOException, IllegalArgumentException,
NoSuchElementException {
//read the regular feature
SimpleFeature f = super.next();
//rebuild it with the join feature type
joinFeatureBuilder.init(f);
f = joinFeatureBuilder.buildFeature(f.getID());
//add additional attributes for joined features
for (int i = 0; i < joinReaders.size(); i++) {
JDBCFeatureReader r = joinReaders.get(i);
f.setAttribute(f.getAttributeCount() - joinReaders.size() + i, r.next());
}
return f;
}
@Override
public void close() throws IOException {
super.close();
//we don't need to close the delegate readers because they share the same result set
// and connection as this reader
}
static SimpleFeatureType retype(SimpleFeatureType featureType, JoinInfo join) {
SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder();
b.init(featureType);
for (JoinPart part : join.getParts()) {
b.add(part.getAttributeName(), SimpleFeature.class);
}
return b.buildFeatureType();
}
}