/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, 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.arcsde.data; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.arcsde.ArcSdeException; import org.geotools.arcsde.session.ISession; import org.geotools.arcsde.session.SdeRow; import org.geotools.data.AttributeReader; import org.geotools.data.DataSourceException; import org.geotools.util.logging.Logging; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import com.esri.sde.sdk.client.SeException; import com.esri.sde.sdk.client.SeQuery; import com.esri.sde.sdk.client.SeShape; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Implements an attribute reader that is aware of the particulars of ArcSDE. This class sends its * logging to the log named "org.geotools.data". * * @author Gabriel Roldan, Axios Engineering * @source $URL: * http://svn.geotools.org/geotools/trunk/gt/modules/plugin/arcsde/datastore/src/main/java * /org/geotools/arcsde/data/ArcSDEAttributeReader.java $ * @version $Id$ */ final class ArcSDEAttributeReader implements AttributeReader { /** Shared package's logger */ private static final Logger LOGGER = Logging.getLogger(ArcSDEAttributeReader.class.getName()); /** query passed to the constructor */ private ArcSDEQuery query; /** schema of the features this attribute reader iterates over */ private final SimpleFeatureType schema; /** current sde java api row being read */ private SdeRow currentRow; /** * the unique id of the current feature. -1 means the feature id was not retrieved */ private long currentFid = -1; /** * holds the "<DATABASE_NAME>.<USER_NAME>." string and is used to efficiently create * String FIDs from the SeShape feature id, which is a long number. */ private StringBuffer fidPrefix; /** * lenght of the prefix string for creating string based feature ids, used to truncate the * <code>fidPrefix</code> and append it the SeShape's feature id number */ private int fidPrefixLen; /** * Strategy to read FIDs */ private FIDReader fidReader; /** * flag to avoid the processing done in <code>hasNext()</code> if next() was not called between * calls to hasNext() */ private boolean hasNextAlreadyCalled = false; /** * Declared binding for the schema's default geometry */ private final Class<? extends Geometry> schemaGeometryClass; private ISession session; private GeometryFactory geometryFactory; /** * The query that defines this readers interaction with an ArcSDE instance. * * @param query * the {@link SeQuery} wrapper where to fetch rows from. Must NOT be already * {@link ArcSDEQuery#execute() executed}. * @param geometryFactory * the JTS GeometryFactory to use when creating Feature geometries * @param session * the session the <code>query</code> is being ran over. This attribute reader will * close it only if it does not have a transaction in progress. * @throws IOException */ @SuppressWarnings("unchecked") public ArcSDEAttributeReader(final ArcSDEQuery query, final GeometryFactory geometryFactory, final ISession session) throws IOException { this.query = query; this.session = session; this.fidReader = query.getFidReader(); this.schema = query.getSchema(); this.geometryFactory = geometryFactory; String typeName = schema.getTypeName(); this.fidPrefix = new StringBuffer(typeName).append('.'); this.fidPrefixLen = this.fidPrefix.length(); final GeometryDescriptor geomType = schema.getGeometryDescriptor(); if (geomType != null) { this.schemaGeometryClass = (Class<? extends Geometry>) geomType.getType().getBinding(); } else { this.schemaGeometryClass = null; } query.execute(); } /** * */ public int getAttributeCount() { return this.schema.getAttributeCount(); } /** * */ public AttributeDescriptor getAttributeType(int index) throws ArrayIndexOutOfBoundsException { return this.schema.getDescriptor(index); } /** * Closes the associated query object and, if this attribute reader is not being run over a * connection with a transaction in progress, closes the connection too. */ public void close() throws IOException { if (query != null) { LOGGER.finest("Closing ArcSDEAttributeReader for " + schema.getTypeName()); try { query.close(); } finally { session.dispose(); query = null; session = null; fidReader = null; currentRow = null; } } } /** * */ public boolean hasNext() throws IOException { if (!this.hasNextAlreadyCalled) { try { currentRow = query.fetch(); if (currentRow == null) { this.query.close(); } else { this.currentFid = fidReader.readFid(currentRow); } hasNextAlreadyCalled = true; } catch (IOException dse) { this.hasNextAlreadyCalled = true; close(); LOGGER.log(Level.SEVERE, dse.getLocalizedMessage(), dse); throw dse; } catch (RuntimeException ex) { this.hasNextAlreadyCalled = true; close(); throw new DataSourceException("Fetching row:" + ex.getMessage(), ex); } } boolean hasNext = this.currentRow != null; if (!hasNext) { // be cautious of clients not calling close and letting stale // connections // TODO: it might be better to do a sanity check on finalize() // and require client code to respect contract. close(); } return hasNext; } /** * Retrieves the next row, or throws a DataSourceException if not more rows are available. * * @throws IOException */ public void next() throws IOException { if (this.currentRow == null) { throw new DataSourceException("There are no more rows"); } this.hasNextAlreadyCalled = false; } /** * * @param index * @return * @throws IOException * never, since the feature retrieve was done in <code>hasNext()</code> * @throws ArrayIndexOutOfBoundsException * if <code>index</code> is outside the bounds of the schema attribute's count */ public Object read(final int index) throws IOException, ArrayIndexOutOfBoundsException { Object value = currentRow.getObject(index); if (value instanceof SeShape) { try { final SeShape shape = (SeShape) value; final Class<? extends Geometry> actualGeomtryClass; if (shape.isNil()) { // actualGeomtryClass = this.schemaGeometryClass; value = null; } else { actualGeomtryClass = ArcSDEAdapter.getGeometryTypeFromSeShape(shape); final ArcSDEGeometryBuilder geometryBuilder; geometryBuilder = ArcSDEGeometryBuilder.builderFor(actualGeomtryClass); value = geometryBuilder.construct(shape, geometryFactory); if (!this.schemaGeometryClass.isAssignableFrom(actualGeomtryClass)) { value = adaptGeometry((Geometry) value, schemaGeometryClass); } } } catch (SeException e) { throw new ArcSdeException(e); } } else if (value instanceof Geometry) { if (!this.schemaGeometryClass.isAssignableFrom(value.getClass())) { value = adaptGeometry((Geometry) value, schemaGeometryClass); } } return value; } private Geometry adaptGeometry(final Geometry value, Class<? extends Geometry> targetType) { final Class<? extends Geometry> currentClass = value.getClass(); final GeometryFactory factory = value.getFactory(); Geometry adapted; if (MultiPoint.class == targetType && Point.class == currentClass) { adapted = factory.createMultiPoint(value.getCoordinates()); } else if (MultiLineString.class == targetType && LineString.class == currentClass) { adapted = factory.createMultiLineString(new LineString[] { (LineString) value }); } else if (MultiPolygon.class == targetType && Polygon.class == currentClass) { adapted = factory.createMultiPolygon(new Polygon[] { (Polygon) value }); } else { throw new IllegalArgumentException("Don't know how to adapt " + currentClass.getName() + " to " + targetType.getName()); } return adapted; } public Object[] readAll() throws ArrayIndexOutOfBoundsException, IOException { int size = schema.getAttributeCount(); Object[] all = new Object[size]; for (int i = 0; i < size; i++) { all[i] = read(i); } return all; } /** * */ public String readFID() throws IOException { if (this.currentFid == -1) { throw new DataSourceException("The feature id was not fetched"); } this.fidPrefix.setLength(this.fidPrefixLen); this.fidPrefix.append(this.currentFid); return this.fidPrefix.toString(); } SimpleFeatureType getFeatureType() { return schema; } }