package com.revolsys.gis.esri.gdb.file; import java.io.File; import java.lang.reflect.Constructor; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import com.revolsys.beans.ObjectException; import com.revolsys.beans.ObjectPropertyException; import com.revolsys.collection.iterator.AbstractIterator; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.ClockDirection; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.gis.esri.gdb.file.capi.FileGdbDomainCodeTable; import com.revolsys.gis.esri.gdb.file.capi.swig.EnumRows; import com.revolsys.gis.esri.gdb.file.capi.swig.Envelope; import com.revolsys.gis.esri.gdb.file.capi.swig.EsriFileGdb; import com.revolsys.gis.esri.gdb.file.capi.swig.Geodatabase; import com.revolsys.gis.esri.gdb.file.capi.swig.Row; import com.revolsys.gis.esri.gdb.file.capi.swig.Table; import com.revolsys.gis.esri.gdb.file.capi.swig.VectorOfWString; import com.revolsys.gis.esri.gdb.file.capi.type.AbstractFileGdbFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.AreaFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.BinaryFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.DateFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.DoubleFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.FloatFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.GeometryFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.GlobalIdFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.GuidFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.IntegerFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.LengthFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.OidFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.ShortFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.StringFieldDefinition; import com.revolsys.gis.esri.gdb.file.capi.type.XmlFieldDefinition; import com.revolsys.identifier.Identifier; import com.revolsys.identifier.SingleIdentifier; import com.revolsys.io.FileUtil; import com.revolsys.io.PathName; import com.revolsys.io.PathUtil; import com.revolsys.io.Writer; import com.revolsys.jdbc.JdbcUtils; import com.revolsys.logging.Logs; import com.revolsys.parallel.SingleThreadExecutor; import com.revolsys.record.Record; import com.revolsys.record.RecordState; import com.revolsys.record.code.CodeTable; import com.revolsys.record.io.RecordWriter; import com.revolsys.record.io.format.esri.gdb.xml.EsriGeodatabaseXmlConstants; import com.revolsys.record.io.format.esri.gdb.xml.model.DEFeatureClass; import com.revolsys.record.io.format.esri.gdb.xml.model.DEFeatureDataset; import com.revolsys.record.io.format.esri.gdb.xml.model.DETable; import com.revolsys.record.io.format.esri.gdb.xml.model.Domain; import com.revolsys.record.io.format.esri.gdb.xml.model.EsriGdbXmlParser; import com.revolsys.record.io.format.esri.gdb.xml.model.EsriGdbXmlSerializer; import com.revolsys.record.io.format.esri.gdb.xml.model.EsriXmlRecordDefinitionUtil; import com.revolsys.record.io.format.esri.gdb.xml.model.Field; import com.revolsys.record.io.format.esri.gdb.xml.model.Index; import com.revolsys.record.io.format.esri.gdb.xml.model.SpatialReference; import com.revolsys.record.io.format.esri.gdb.xml.model.enums.FieldType; import com.revolsys.record.io.format.xml.XmlProcessor; import com.revolsys.record.property.LengthFieldName; import com.revolsys.record.query.AbstractMultiCondition; import com.revolsys.record.query.BinaryCondition; import com.revolsys.record.query.CollectionValue; import com.revolsys.record.query.Column; import com.revolsys.record.query.Condition; import com.revolsys.record.query.ILike; import com.revolsys.record.query.In; import com.revolsys.record.query.LeftUnaryCondition; import com.revolsys.record.query.Like; import com.revolsys.record.query.Query; import com.revolsys.record.query.QueryValue; import com.revolsys.record.query.RightUnaryCondition; import com.revolsys.record.query.SqlCondition; import com.revolsys.record.query.Value; import com.revolsys.record.query.functions.EnvelopeIntersects; import com.revolsys.record.query.functions.WithinDistance; import com.revolsys.record.schema.AbstractRecordStore; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.record.schema.RecordDefinitionImpl; import com.revolsys.record.schema.RecordStoreSchema; import com.revolsys.record.schema.RecordStoreSchemaElement; import com.revolsys.util.Dates; import com.revolsys.util.Exceptions; import com.revolsys.util.JavaBeanUtil; import com.revolsys.util.Property; import com.revolsys.util.StringBuilders; public class FileGdbRecordStore extends AbstractRecordStore { private static final Object API_SYNC = new Object(); private static final Map<FieldType, Constructor<? extends AbstractFileGdbFieldDefinition>> ESRI_FIELD_TYPE_FIELD_DEFINITION_MAP = new HashMap<>(); private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\?"); private static final SingleThreadExecutor TASK_EXECUTOR = new SingleThreadExecutor( "ESRI FGDB Create Thread"); static { addFieldTypeConstructor(FieldType.esriFieldTypeInteger, IntegerFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeSmallInteger, ShortFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeDouble, DoubleFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeSingle, FloatFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeString, StringFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeDate, DateFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeGeometry, GeometryFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeOID, OidFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeBlob, BinaryFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeGlobalID, GlobalIdFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeGUID, GuidFieldDefinition.class); addFieldTypeConstructor(FieldType.esriFieldTypeXML, XmlFieldDefinition.class); } private static void addFieldTypeConstructor(final FieldType fieldType, final Class<? extends AbstractFileGdbFieldDefinition> fieldClass) { try { final Constructor<? extends AbstractFileGdbFieldDefinition> constructor = fieldClass .getConstructor(Field.class); ESRI_FIELD_TYPE_FIELD_DEFINITION_MAP.put(fieldType, constructor); } catch (final SecurityException e) { Logs.error(FileGdbRecordStore.class, "No public constructor for ESRI type " + fieldType, e); } catch (final NoSuchMethodException e) { Logs.error(FileGdbRecordStore.class, "No public constructor for ESRI type " + fieldType, e); } } private static <V> V getSingleThreadResult(final Callable<V> callable) { synchronized (API_SYNC) { return TASK_EXECUTOR.call(callable); } } public static SpatialReference getSpatialReference(final GeometryFactory geometryFactory) { if (geometryFactory == null || geometryFactory.getCoordinateSystemId() == 0) { return null; } else { final String wkt = getSingleThreadResult(() -> { return EsriFileGdb.getSpatialReferenceWkt(geometryFactory.getCoordinateSystemId()); }); final SpatialReference spatialReference = SpatialReference.get(geometryFactory, wkt); return spatialReference; } } private final Object apiSync = new Object(); private final Map<PathName, String> catalogPathByPath = new HashMap<>(); private boolean createMissingRecordStore = true; private boolean createMissingTables = true; private PathName defaultSchemaPath = PathName.ROOT; private Map<String, List<String>> domainFieldNames = new HashMap<>(); private boolean exists = false; private String fileName; private Geodatabase geodatabase; private int geodatabaseReferenceCount; private final Map<PathName, AtomicLong> idGenerators = new HashMap<>(); private boolean initialized; private final Map<String, Table> tableByCatalogPath = new HashMap<>(); private final Map<String, Integer> tableReferenceCountsByCatalogPath = new HashMap<>(); private final Map<String, Integer> tableWriteLockCountsByCatalogPath = new HashMap<>(); private boolean createLengthField = false; private boolean createAreaField = false; FileGdbRecordStore(final File file) { this.fileName = FileUtil.getCanonicalPath(file); setConnectionProperties(Collections.singletonMap("url", FileUtil.toUrl(file).toString())); this.catalogPathByPath.put(PathName.ROOT, "\\"); } @Override public void addCodeTable(final CodeTable codeTable) { super.addCodeTable(codeTable); synchronized (this.apiSync) { synchronized (API_SYNC) { if (codeTable instanceof Domain) { final Domain domain = (Domain)codeTable; newDomainCodeTable(domain); } } } } public void alterDomain(final Domain domain) { final String domainDefinition = EsriGdbXmlSerializer.toString(domain); synchronized (this.apiSync) { synchronized (API_SYNC) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { geodatabase.alterDomain(domainDefinition); } finally { releaseGeodatabase(); } } } } } @Override public void appendQueryValue(final Query query, final StringBuilder buffer, final QueryValue condition) { if (condition instanceof Like || condition instanceof ILike) { final BinaryCondition like = (BinaryCondition)condition; final QueryValue left = like.getLeft(); final QueryValue right = like.getRight(); buffer.append("UPPER(CAST("); appendQueryValue(query, buffer, left); buffer.append(" AS VARCHAR(4000))) LIKE "); if (right instanceof Value) { final Value valueCondition = (Value)right; final Object value = valueCondition.getValue(); buffer.append("'"); if (value != null) { final String string = DataTypes.toString(value); buffer.append(string.toUpperCase().replaceAll("'", "''")); } buffer.append("'"); } else { appendQueryValue(query, buffer, right); } } else if (condition instanceof LeftUnaryCondition) { final LeftUnaryCondition unaryCondition = (LeftUnaryCondition)condition; final String operator = unaryCondition.getOperator(); final QueryValue right = unaryCondition.getValue(); buffer.append(operator); buffer.append(" "); appendQueryValue(query, buffer, right); } else if (condition instanceof RightUnaryCondition) { final RightUnaryCondition unaryCondition = (RightUnaryCondition)condition; final QueryValue left = unaryCondition.getValue(); final String operator = unaryCondition.getOperator(); appendQueryValue(query, buffer, left); buffer.append(" "); buffer.append(operator); } else if (condition instanceof BinaryCondition) { final BinaryCondition binaryCondition = (BinaryCondition)condition; final QueryValue left = binaryCondition.getLeft(); final String operator = binaryCondition.getOperator(); final QueryValue right = binaryCondition.getRight(); appendQueryValue(query, buffer, left); buffer.append(" "); buffer.append(operator); buffer.append(" "); appendQueryValue(query, buffer, right); } else if (condition instanceof AbstractMultiCondition) { final AbstractMultiCondition multipleCondition = (AbstractMultiCondition)condition; buffer.append("("); boolean first = true; final String operator = multipleCondition.getOperator(); for (final QueryValue subCondition : multipleCondition.getQueryValues()) { if (first) { first = false; } else { buffer.append(" "); buffer.append(operator); buffer.append(" "); } appendQueryValue(query, buffer, subCondition); } buffer.append(")"); } else if (condition instanceof In) { final In in = (In)condition; if (in.isEmpty()) { buffer.append("1==0"); } else { final QueryValue left = in.getLeft(); appendQueryValue(query, buffer, left); buffer.append(" IN ("); appendQueryValue(query, buffer, in.getValues()); buffer.append(")"); } } else if (condition instanceof Value) { final Value valueCondition = (Value)condition; Object value = valueCondition.getValue(); if (value instanceof Identifier) { final Identifier identifier = (Identifier)value; value = identifier.getValue(0); } appendValue(buffer, value); } else if (condition instanceof CollectionValue) { final CollectionValue collectionValue = (CollectionValue)condition; final List<Object> values = collectionValue.getValues(); boolean first = true; for (final Object value : values) { if (first) { first = false; } else { buffer.append(", "); } appendValue(buffer, value); } } else if (condition instanceof Column) { final Column column = (Column)condition; final Object name = column.getName(); buffer.append(name); } else if (condition instanceof SqlCondition) { final SqlCondition sqlCondition = (SqlCondition)condition; final String where = sqlCondition.getSql(); final List<Object> parameters = sqlCondition.getParameterValues(); if (parameters.isEmpty()) { if (where.indexOf('?') > -1) { throw new IllegalArgumentException( "No arguments specified for a where clause with placeholders: " + where); } else { buffer.append(where); } } else { final Matcher matcher = PLACEHOLDER_PATTERN.matcher(where); int i = 0; while (matcher.find()) { if (i >= parameters.size()) { throw new IllegalArgumentException( "Not enough arguments for where clause with placeholders: " + where); } final Object argument = parameters.get(i); final StringBuffer replacement = new StringBuffer(); matcher.appendReplacement(replacement, DataTypes.toString(argument)); buffer.append(replacement); appendValue(buffer, argument); i++; } final StringBuffer tail = new StringBuffer(); matcher.appendTail(tail); buffer.append(tail); } } else if (condition instanceof EnvelopeIntersects) { buffer.append("1 = 1"); } else if (condition instanceof WithinDistance) { buffer.append("1 = 1"); } else { condition.appendDefaultSql(query, this, buffer); } } public void appendValue(final StringBuilder buffer, Object value) { if (value instanceof SingleIdentifier) { final SingleIdentifier identifier = (SingleIdentifier)value; value = identifier.getValue(0); } if (value == null) { buffer.append("''"); } else if (value instanceof Number) { buffer.append(value); } else if (value instanceof java.util.Date) { final String stringValue = Dates.format("yyyy-MM-dd", (java.util.Date)value); buffer.append("DATE '" + stringValue + "'"); } else { final Object value1 = value; final String stringValue = DataTypes.toString(value1); buffer.append("'"); buffer.append(stringValue.replaceAll("'", "''")); buffer.append("'"); } } @Override @PreDestroy public void close() { if (FileGdbRecordStoreFactory.release(this)) { closeDo(); } } public void closeDo() { this.exists = false; synchronized (this.apiSync) { synchronized (API_SYNC) { try { if (!isClosed()) { if (this.geodatabase != null) { final Writer<Record> writer = getThreadProperty("writer"); if (writer != null) { writer.close(); setThreadProperty("writer", null); } closeTables(); try { if (this.geodatabase != null) { closeGeodatabase(this.geodatabase); } } finally { this.geodatabase = null; } } } } finally { super.close(); } } } } private void closeGeodatabase(final Geodatabase geodatabase) { if (geodatabase != null) { final Integer closeResult = getSingleThreadResult(() -> { return EsriFileGdb.CloseGeodatabase(geodatabase); }); if (closeResult != null && closeResult != 0) { Logs.error(this, "Error closing: " + this.fileName + " ESRI Error=" + closeResult); } } } public boolean closeTable(final PathName typePath) { synchronized (this.apiSync) { final String path = getCatalogPath(typePath); int count = Maps.getInteger(this.tableReferenceCountsByCatalogPath, path, 0); count--; if (count <= 0) { this.tableReferenceCountsByCatalogPath.remove(path); final Table table = this.tableByCatalogPath.remove(path); synchronized (API_SYNC) { if (table != null) { try { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { geodatabase.closeTable(table); } finally { releaseGeodatabase(); } } } catch (final Throwable e) { Logs.error(this, "Cannot close Table " + typePath, e); } finally { try { table.delete(); } catch (final Throwable t) { } } } } return true; } else { this.tableReferenceCountsByCatalogPath.put(path, count); return false; } } } private void closeTables() { synchronized (this.apiSync) { if (!this.tableByCatalogPath.isEmpty()) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { for (final Table table : this.tableByCatalogPath.values()) { try { table.setLoadOnlyMode(false); table.freeWriteLock(); geodatabase.closeTable(table); } catch (final Throwable e) { } finally { try { table.delete(); } catch (final Throwable t) { } } } this.tableByCatalogPath.clear(); this.tableReferenceCountsByCatalogPath.clear(); this.tableWriteLockCountsByCatalogPath.clear(); } finally { releaseGeodatabase(); } } } } } public void deleteGeodatabase() { synchronized (this.apiSync) { this.createMissingRecordStore = false; this.createMissingTables = false; final String fileName = this.fileName; try { closeDo(); } finally { if (new File(fileName).exists()) { final Integer deleteResult = getSingleThreadResult(() -> { return EsriFileGdb.DeleteGeodatabase(fileName); }); if (deleteResult != null && deleteResult != 0) { Logs.error(this, "Error deleting: " + fileName + " ESRI Error=" + deleteResult); } } } } } @Override public boolean deleteRecord(final Record record) { if (record == null) { return false; } else { final RecordDefinition recordDefinition = record.getRecordDefinition(); final Table table = getTableWithWriteLock(recordDefinition); try { return deleteRecord(table, record); } finally { releaseTableAndWriteLock(recordDefinition); } } } boolean deleteRecord(final Table table, final Record record) { final Integer objectId = record.getInteger("OBJECTID"); final PathName typePath = record.getPathName(); if (objectId != null && table != null) { synchronized (table) { final String whereClause = "OBJECTID=" + objectId; try ( final FileGdbEnumRowsIterator rows = search(typePath, table, "OBJECTID", whereClause, false)) { for (final Row row : rows) { synchronized (this.apiSync) { final boolean loadOnly = isTableLocked(typePath); if (loadOnly) { table.setLoadOnlyMode(false); } table.deleteRow(row); if (loadOnly) { table.setLoadOnlyMode(true); } } record.setState(RecordState.DELETED); addStatistic("Delete", record); return true; } } } } return false; } @Override protected void finalize() throws Throwable { super.finalize(); } public Object getApiSync() { return this.apiSync; } protected String getCatalogPath(final PathName path) { final String catalogPath = this.catalogPathByPath.get(path); if (Property.hasValue(catalogPath)) { return catalogPath; } else { return toCatalogPath(path); } } protected String getCatalogPath(final RecordStoreSchemaElement element) { final PathName path = element.getPathName(); return getCatalogPath(path); } private VectorOfWString getChildDatasets(final Geodatabase geodatabase, final String catalogPath, final String datasetType) { final boolean pathExists = isPathExists(geodatabase, catalogPath); if (pathExists) { return geodatabase.getChildDatasets(catalogPath, datasetType); } else { return null; } } public PathName getDefaultSchemaPath() { return this.defaultSchemaPath; } public Map<String, List<String>> getDomainFieldNames() { return this.domainFieldNames; } public final String getFileName() { return this.fileName; } private Geodatabase getGeodatabase() { synchronized (this.apiSync) { if (isExists()) { this.geodatabaseReferenceCount++; if (this.geodatabase == null) { this.geodatabase = openGeodatabase(); } return this.geodatabase; } else { return null; } } } @Override public Record getRecord(final PathName typePath, final Object... id) { synchronized (this.apiSync) { final RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { throw new IllegalArgumentException("Unknown type " + typePath); } else { final String catalogPath = getCatalogPath(typePath); final FileGdbQueryIterator iterator = new FileGdbQueryIterator(this, catalogPath, recordDefinition.getIdFieldName() + " = " + id[0]); try { if (iterator.hasNext()) { return iterator.next(); } else { return null; } } finally { iterator.close(); } } } } @Override public int getRecordCount(final Query query) { if (query == null) { return 0; } else { synchronized (this.apiSync) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase == null) { return 0; } else { try { String typePath = query.getTypeName(); RecordDefinition recordDefinition = query.getRecordDefinition(); if (recordDefinition == null) { typePath = query.getTypeName(); recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { return 0; } } else { typePath = recordDefinition.getPath(); } final StringBuilder whereClause = getWhereClause(query); final BoundingBox boundingBox = QueryValue.getBoundingBox(query); if (boundingBox == null) { final StringBuilder sql = new StringBuilder(); sql.append("SELECT OBJECTID FROM "); sql.append(JdbcUtils.getTableName(typePath)); if (whereClause.length() > 0) { sql.append(" WHERE "); sql.append(whereClause); } try ( final FileGdbEnumRowsIterator rows = query(sql.toString(), false)) { int count = 0; for (@SuppressWarnings("unused") final Row row : rows) { count++; } return count; } } else { final GeometryFieldDefinition geometryField = (GeometryFieldDefinition)recordDefinition .getGeometryField(); if (geometryField == null || boundingBox.isEmpty()) { return 0; } else { final StringBuilder sql = new StringBuilder(); sql.append("SELECT " + geometryField.getName() + " FROM "); sql.append(JdbcUtils.getTableName(typePath)); if (whereClause.length() > 0) { sql.append(" WHERE "); sql.append(whereClause); } try ( final FileGdbEnumRowsIterator rows = query(sql.toString(), false)) { int count = 0; for (final Row row : rows) { final Geometry geometry = (Geometry)geometryField.getValue(row); if (geometry != null) { final BoundingBox geometryBoundingBox = geometry.getBoundingBox(); if (geometryBoundingBox.intersects(boundingBox)) { count++; } } } return count; } } } } finally { releaseGeodatabase(); } } } } } public RecordDefinitionImpl getRecordDefinition(final PathName schemaName, final String path, final String tableDefinition) { synchronized (this.apiSync) { synchronized (API_SYNC) { try { final XmlProcessor parser = new EsriGdbXmlParser(); final DETable deTable = parser.process(tableDefinition); final String tableName = deTable.getName(); final PathName typePath = PathName.newPathName(schemaName.newChild(tableName)); final RecordStoreSchema schema = getSchema(schemaName); final RecordDefinitionImpl recordDefinition = new RecordDefinitionImpl(schema, typePath); recordDefinition.setPolygonRingDirection(ClockDirection.NONE); String lengthFieldName = null; String areaFieldName = null; if (deTable instanceof DEFeatureClass) { final DEFeatureClass featureClass = (DEFeatureClass)deTable; lengthFieldName = featureClass.getLengthFieldName(); final LengthFieldName lengthFieldNameProperty = new LengthFieldName(lengthFieldName); lengthFieldNameProperty.setRecordDefinition(recordDefinition); areaFieldName = featureClass.getAreaFieldName(); final LengthFieldName areaFieldNameProperty = new LengthFieldName(areaFieldName); areaFieldNameProperty.setRecordDefinition(recordDefinition); } for (final Field field : deTable.getFields()) { final String fieldName = field.getName(); AbstractFileGdbFieldDefinition fieldDefinition = null; if (fieldName.equals(lengthFieldName)) { fieldDefinition = new LengthFieldDefinition(field); } else if (fieldName.equals(areaFieldName)) { fieldDefinition = new AreaFieldDefinition(field); } else { final FieldType type = field.getType(); final Constructor<? extends AbstractFileGdbFieldDefinition> fieldConstructor = ESRI_FIELD_TYPE_FIELD_DEFINITION_MAP .get(type); if (fieldConstructor != null) { try { fieldDefinition = JavaBeanUtil.invokeConstructor(fieldConstructor, field); } catch (final Throwable e) { Logs.error(this, tableDefinition); throw new RuntimeException("Error creating field for " + typePath + "." + field.getName() + " : " + field.getType(), e); } } else { Logs.error(this, "Unsupported field type " + fieldName + ":" + type); } } if (fieldDefinition != null) { final Domain domain = field.getDomain(); if (domain != null) { CodeTable codeTable = getCodeTable(domain.getDomainName() + "_ID"); if (codeTable == null) { codeTable = new FileGdbDomainCodeTable(this, domain); addCodeTable(codeTable); } fieldDefinition.setCodeTable(codeTable); } fieldDefinition.setRecordStore(this); recordDefinition.addField(fieldDefinition); if (fieldDefinition instanceof GlobalIdFieldDefinition) { recordDefinition.setIdFieldName(fieldName); } } } final String oidFieldName = deTable.getOIDFieldName(); recordDefinition.setProperty(EsriGeodatabaseXmlConstants.ESRI_OBJECT_ID_FIELD_NAME, oidFieldName); if (deTable instanceof DEFeatureClass) { final DEFeatureClass featureClass = (DEFeatureClass)deTable; final String shapeFieldName = featureClass.getShapeFieldName(); recordDefinition.setGeometryFieldName(shapeFieldName); } for (final Index index : deTable.getIndexes()) { if (index.getName().endsWith("_PK")) { for (final Field field : index.getFields()) { final String fieldName = field.getName(); recordDefinition.setIdFieldName(fieldName); } } } addRecordDefinitionProperties(recordDefinition); if (recordDefinition.getIdFieldIndex() == -1) { recordDefinition.setIdFieldName(deTable.getOIDFieldName()); } this.catalogPathByPath.put(typePath, deTable.getCatalogPath()); return recordDefinition; } catch (final RuntimeException e) { Logs.debug(this, tableDefinition); throw e; } } } } @Override public RecordDefinition getRecordDefinition(final RecordDefinition sourceRecordDefinition) { synchronized (this.apiSync) { if (getGeometryFactory() == null) { setGeometryFactory(sourceRecordDefinition.getGeometryFactory()); } final String typePath = sourceRecordDefinition.getPath(); RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { if (!sourceRecordDefinition.hasGeometryField()) { recordDefinition = getRecordDefinition(PathUtil.getName(typePath)); } if (this.createMissingTables && recordDefinition == null) { recordDefinition = newTableRecordDefinition(sourceRecordDefinition); } } return recordDefinition; } } @Override public String getRecordStoreType() { return FileGdbRecordStoreFactory.DESCRIPTION; } protected Table getTable(final RecordDefinition recordDefinition) { synchronized (this.apiSync) { synchronized (API_SYNC) { final RecordDefinition fgdbRecordDefinition = getRecordDefinition(recordDefinition); if (!isExists() || fgdbRecordDefinition == null) { return null; } else { try { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase == null) { return null; } else { final String catalogPath = getCatalogPath(fgdbRecordDefinition); try { Table table = this.tableByCatalogPath.get(catalogPath); if (table == null) { table = this.geodatabase.openTable(catalogPath); if (table != null) { if (this.tableByCatalogPath.isEmpty()) { this.geodatabaseReferenceCount++; } Maps.addCount(this.tableReferenceCountsByCatalogPath, catalogPath); this.tableByCatalogPath.put(catalogPath, table); } } else { Maps.addCount(this.tableReferenceCountsByCatalogPath, catalogPath); } return table; } catch (final RuntimeException e) { throw new RuntimeException("Unable to open table " + catalogPath, e); } } } finally { releaseGeodatabase(); } } } } } protected Table getTableWithWriteLock(final RecordDefinition recordDefinition) { synchronized (this.apiSync) { final Table table = getTable(recordDefinition); if (table != null) { final String catalogPath = getCatalogPath(recordDefinition); final Integer count = Maps.addCount(this.tableWriteLockCountsByCatalogPath, catalogPath); if (count == 1) { table.setWriteLock(); table.setLoadOnlyMode(true); } } return table; } } protected StringBuilder getWhereClause(final Query query) { final StringBuilder whereClause = new StringBuilder(); final Condition whereCondition = query.getWhereCondition(); if (!whereCondition.isEmpty()) { appendQueryValue(query, whereClause, whereCondition); } return whereClause; } protected boolean hasCatalogPath(final String path) { final String catalogPath = this.catalogPathByPath.get(path); return catalogPath != null; } private boolean hasChildDataset(final Geodatabase geodatabase, final String parentCatalogPath, final String datasetType, final String childCatalogPath) { try { final VectorOfWString childDatasets = geodatabase.getChildDatasets(parentCatalogPath, datasetType); for (int i = 0; i < childDatasets.size(); i++) { final String catalogPath = childDatasets.get(i); if (catalogPath.equals(childCatalogPath)) { return true; } } return false; } catch (final RuntimeException e) { if ("-2147211775\tThe item was not found.".equals(e.getMessage())) { return false; } else { throw e; } } } @Override @PostConstruct public void initialize() { synchronized (this.apiSync) { synchronized (API_SYNC) { if (!this.initialized) { Geodatabase geodatabase = null; this.initialized = true; try { super.initialize(); final File file = new File(this.fileName); if (file.exists()) { if (file.isDirectory()) { if (!new File(this.fileName, "gdb").exists()) { throw new IllegalArgumentException( FileUtil.getCanonicalPath(file) + " is not a valid ESRI File Geodatabase"); } geodatabase = getSingleThreadResult(() -> { return EsriFileGdb.openGeodatabase(this.fileName); }); } else { throw new IllegalArgumentException( FileUtil.getCanonicalPath(file) + " ESRI File Geodatabase must be a directory"); } } else if (this.createMissingRecordStore) { geodatabase = newGeodatabase(); } else { throw new IllegalArgumentException( "ESRI file geodatabase not found " + this.fileName); } final VectorOfWString domainNames = geodatabase.getDomains(); for (int i = 0; i < domainNames.size(); i++) { final String domainName = domainNames.get(i); loadDomain(geodatabase, domainName); } this.exists = true; } catch (final Throwable e) { try { closeDo(); } finally { Exceptions.throwUncheckedException(e); } } finally { if (geodatabase != null) { closeGeodatabase(geodatabase); } } } } } } @Override public void insertRecord(final Record record) { if (record == null) { } else { final RecordDefinition recordDefinition = record.getRecordDefinition(); final Table table = getTableWithWriteLock(recordDefinition); try { insertRecord(table, record); } finally { releaseTableAndWriteLock(recordDefinition); } } } void insertRecord(final Table table, final Record record) { final RecordDefinition sourceRecordDefinition = record.getRecordDefinition(); final RecordDefinition recordDefinition = getRecordDefinition(sourceRecordDefinition); validateRequired(record, recordDefinition); final PathName typePath = recordDefinition.getPathName(); if (table == null) { throw new ObjectException(record, "Cannot find table: " + typePath); } else { try { final Row row = newRowObject(table); try { for (final FieldDefinition field : recordDefinition.getFields()) { final String name = field.getName(); try { final Object value = record.getValue(name); final AbstractFileGdbFieldDefinition esriField = (AbstractFileGdbFieldDefinition)field; esriField.setInsertValue(record, row, value); } catch (final Throwable e) { throw new ObjectPropertyException(record, name, e); } } insertRow(table, row); if (sourceRecordDefinition == recordDefinition) { for (final FieldDefinition field : recordDefinition.getFields()) { final AbstractFileGdbFieldDefinition esriField = (AbstractFileGdbFieldDefinition)field; try { esriField.setPostInsertValue(record, row); } catch (final Throwable e) { throw new ObjectPropertyException(record, field.getName(), e); } } record.setState(RecordState.PERSISTED); } } finally { row.delete(); addStatistic("Insert", record); } } catch (final ObjectException e) { if (e.getObject() == record) { throw e; } else { throw new ObjectException(record, e); } } catch (final Throwable e) { throw new ObjectException(record, e); } } } protected void insertRow(final Table table, final Row row) { synchronized (this.apiSync) { if (isOpen(table)) { table.insertRow(row); } } } public boolean isCreateAreaField() { return this.createAreaField; } public boolean isCreateLengthField() { return this.createLengthField; } public boolean isCreateMissingRecordStore() { return this.createMissingRecordStore; } public boolean isCreateMissingTables() { return this.createMissingTables; } public boolean isExists() { return this.exists && !isClosed(); } public boolean isNull(final Row row, final String name) { synchronized (this.apiSync) { return row.isNull(name); } } public boolean isOpen(final Table table) { synchronized (this.apiSync) { if (table == null) { return false; } else { final boolean open = this.tableByCatalogPath.containsValue(table); return open; } } } private boolean isPathExists(final Geodatabase geodatabase, String path) { if (path == null) { return false; } else if ("\\".equals(path)) { return true; } else { final boolean pathExists = true; path = path.replaceAll("[\\/]+", "\\"); path = path.replaceAll("\\$", ""); int index = 0; while (index != -1) { final String parentPath = path.substring(0, index + 1); final int nextIndex = path.indexOf(index + 1, '\\'); String currentPath; if (nextIndex == -1) { currentPath = path; } else { currentPath = path.substring(0, nextIndex); } boolean found = false; final VectorOfWString children = geodatabase.getChildDatasets(parentPath, "Feature Dataset"); for (int i = 0; i < children.size(); i++) { final String childPath = children.get(i); if (childPath.equals(currentPath)) { found = true; } } if (!found) { return false; } index = nextIndex; } return pathExists; } } private boolean isTableLocked(final PathName typePath) { final String path = getCatalogPath(typePath); return Maps.getCount(this.tableWriteLockCountsByCatalogPath, path) > 0; } protected FileGdbDomainCodeTable loadDomain(final Geodatabase geodatabase, final String domainName) { synchronized (this.apiSync) { synchronized (API_SYNC) { final String domainDef = geodatabase.getDomainDefinition(domainName); final Domain domain = EsriGdbXmlParser.parse(domainDef); if (domain != null) { final FileGdbDomainCodeTable codeTable = new FileGdbDomainCodeTable(this, domain); super.addCodeTable(codeTable); final List<String> fieldNames = this.domainFieldNames.get(domainName); if (fieldNames != null) { for (final String fieldName : fieldNames) { addCodeTable(fieldName, codeTable); } } return codeTable; } } } return null; } public synchronized CodeTable newDomainCodeTable(final Domain domain) { synchronized (this.apiSync) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { final String domainName = domain.getDomainName(); if (!this.domainFieldNames.containsKey(domainName)) { synchronized (API_SYNC) { final String domainDef = EsriGdbXmlSerializer.toString(domain); try { geodatabase.createDomain(domainDef); } catch (final Exception e) { Logs.debug(this, domainDef); Logs.error(this, "Unable to create domain", e); } return loadDomain(geodatabase, domain.getDomainName()); } } } finally { releaseGeodatabase(); } } } return null; } private RecordStoreSchema newFeatureDatasetSchema(final RecordStoreSchema parentSchema, final PathName schemaPath) { final PathName childSchemaPath = schemaPath; final RecordStoreSchema schema = new RecordStoreSchema(parentSchema, childSchemaPath); this.catalogPathByPath.put(childSchemaPath, toCatalogPath(schemaPath)); return schema; } private Geodatabase newGeodatabase() { return getSingleThreadResult(() -> { return EsriFileGdb.createGeodatabase(this.fileName); }); } @Override public AbstractIterator<Record> newIterator(final Query query, final Map<String, Object> properties) { PathName typePath = query.getTypePath(); RecordDefinition recordDefinition = query.getRecordDefinition(); if (recordDefinition == null) { recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { throw new IllegalArgumentException("Type name does not exist " + typePath); } } else { typePath = recordDefinition.getPathName(); } final String catalogPath = getCatalogPath(typePath); final BoundingBox boundingBox = QueryValue.getBoundingBox(query); final Map<String, Boolean> orderBy = query.getOrderBy(); final StringBuilder whereClause = getWhereClause(query); StringBuilder sql = new StringBuilder(); if (orderBy.isEmpty() || boundingBox != null) { if (!orderBy.isEmpty()) { Logs.error(this, "Unable to sort on " + catalogPath + " " + orderBy.keySet() + " as the ESRI library can't sort with a bounding box query"); } sql = whereClause; } else { sql.append("SELECT "); final List<String> fieldNames = query.getFieldNames(); if (fieldNames.isEmpty()) { StringBuilders.append(sql, recordDefinition.getFieldNames()); } else { StringBuilders.append(sql, fieldNames); } sql.append(" FROM "); sql.append(JdbcUtils.getTableName(catalogPath)); if (whereClause.length() > 0) { sql.append(" WHERE "); sql.append(whereClause); } boolean first = true; for (final Entry<String, Boolean> entry : orderBy.entrySet()) { final String column = entry.getKey(); final DataType dataType = recordDefinition.getFieldType(column); if (dataType != null && !Geometry.class.isAssignableFrom(dataType.getJavaClass())) { if (first) { sql.append(" ORDER BY "); first = false; } else { sql.append(", "); } sql.append(column); final Boolean ascending = entry.getValue(); if (!ascending) { sql.append(" DESC"); } } else { Logs.error(this, "Unable to sort on " + recordDefinition.getPath() + "." + column + " as the ESRI library can't sort on " + dataType + " columns"); } } } final FileGdbQueryIterator iterator = new FileGdbQueryIterator(this, catalogPath, sql.toString(), boundingBox, query, query.getOffset(), query.getLimit()); iterator.setStatistics(query.getStatistics()); return iterator; } @Override public Identifier newPrimaryIdentifier(final PathName typePath) { synchronized (this.apiSync) { final RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { return null; } else { final String idFieldName = recordDefinition.getIdFieldName(); if (idFieldName == null) { return null; } else if (!idFieldName.equals("OBJECTID")) { AtomicLong idGenerator = this.idGenerators.get(typePath); if (idGenerator == null) { long maxId = 0; for (final Record record : getRecords(typePath)) { final Identifier id = record.getIdentifier(); final Object firstId = id.getValue(0); if (firstId instanceof Number) { final Number number = (Number)firstId; if (number.longValue() > maxId) { maxId = number.longValue(); } } } idGenerator = new AtomicLong(maxId); this.idGenerators.put(typePath, idGenerator); } return Identifier.newIdentifier(idGenerator.incrementAndGet()); } else { return null; } } } } @Override public FileGdbWriter newRecordWriter() { synchronized (this.apiSync) { FileGdbWriter writer = getThreadProperty("writer"); if (writer == null || writer.isClosed()) { writer = new FileGdbWriter(this); setThreadProperty("writer", writer); } return writer; } } @Override public RecordWriter newRecordWriter(final RecordDefinition recordDefinition) { return new FileGdbWriter(this, recordDefinition); } protected Row newRowObject(final Table table) { synchronized (this.apiSync) { if (isOpen(table)) { return table.createRowObject(); } else { return null; } } } private RecordStoreSchema newSchema(final PathName schemaPath, final SpatialReference spatialReference) { synchronized (this.apiSync) { synchronized (API_SYNC) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase == null) { return null; } else { try { String parentCatalogPath = "\\"; RecordStoreSchema schema = getRootSchema(); for (final PathName childSchemaPath : schemaPath.getPaths()) { if (childSchemaPath.length() > 1) { RecordStoreSchema childSchema = schema.getSchema(childSchemaPath); final String childCatalogPath = toCatalogPath(childSchemaPath); if (!hasChildDataset(getGeodatabase(), parentCatalogPath, "Feature Dataset", childCatalogPath)) { if (spatialReference != null) { final DEFeatureDataset dataset = EsriXmlRecordDefinitionUtil .newDEFeatureDataset(childCatalogPath, spatialReference); final String datasetDefinition = EsriGdbXmlSerializer.toString(dataset); try { geodatabase.createFeatureDataset(datasetDefinition); } catch (final Throwable t) { Logs.debug(this, datasetDefinition); throw new RuntimeException( "Unable to create feature dataset " + childCatalogPath, t); } } } if (childSchema == null) { childSchema = newFeatureDatasetSchema(schema, childSchemaPath); schema.addElement(childSchema); } schema = childSchema; parentCatalogPath = childCatalogPath; } } return schema; } finally { releaseGeodatabase(); } } } } } private RecordDefinitionImpl newTableRecordDefinition(final DETable deTable) { synchronized (this.apiSync) { synchronized (API_SYNC) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase == null) { return null; } else { try { String schemaCatalogPath = deTable.getParentCatalogPath(); SpatialReference spatialReference; if (deTable instanceof DEFeatureClass) { final DEFeatureClass featureClass = (DEFeatureClass)deTable; spatialReference = featureClass.getSpatialReference(); } else { spatialReference = null; } PathName schemaPath = toPath(schemaCatalogPath); final RecordStoreSchema schema = newSchema(schemaPath, spatialReference); if (schemaPath.equals(this.defaultSchemaPath)) { if (!(deTable instanceof DEFeatureClass)) { schemaCatalogPath = "\\"; deTable.setCatalogPath("\\" + deTable.getName()); } } else if (schemaPath.equals("")) { schemaPath = this.defaultSchemaPath; } for (final Field field : deTable.getFields()) { final String fieldName = field.getName(); final CodeTable codeTable = getCodeTableByFieldName(fieldName); if (codeTable instanceof FileGdbDomainCodeTable) { final FileGdbDomainCodeTable domainCodeTable = (FileGdbDomainCodeTable)codeTable; field.setDomain(domainCodeTable.getDomain()); } } final String tableDefinition = EsriGdbXmlSerializer.toString(deTable); try { final Table table = geodatabase.createTable(tableDefinition, schemaCatalogPath); geodatabase.closeTable(table); table.delete(); final RecordDefinitionImpl recordDefinition = getRecordDefinition( PathName.newPathName(schemaPath), schemaCatalogPath, tableDefinition); initRecordDefinition(recordDefinition); schema.addElement(recordDefinition); return recordDefinition; } catch (final Throwable t) { throw new RuntimeException("Unable to create table " + deTable.getCatalogPath(), t); } } finally { releaseGeodatabase(); } } } } } private RecordDefinition newTableRecordDefinition(final RecordDefinition recordDefinition) { synchronized (this.apiSync) { synchronized (API_SYNC) { final GeometryFactory geometryFactory = recordDefinition.getGeometryFactory(); final SpatialReference spatialReference = getSpatialReference(geometryFactory); final DETable deTable = EsriXmlRecordDefinitionUtil.getDETable(recordDefinition, spatialReference, this.createLengthField, this.createAreaField); final RecordDefinitionImpl tableRecordDefinition = newTableRecordDefinition(deTable); final String idFieldName = recordDefinition.getIdFieldName(); if (idFieldName != null) { tableRecordDefinition.setIdFieldName(idFieldName); } return tableRecordDefinition; } } } @Override protected void obtainConnected() { getGeodatabase(); } private Geodatabase openGeodatabase() { return getSingleThreadResult(() -> { try { return EsriFileGdb.openGeodatabase(this.fileName); } catch (final FileGdbException e) { final String message = e.getMessage(); if ("The system cannot find the path specified. (-2147024893)".equals(message)) { return null; } else { throw e; } } }); } public FileGdbEnumRowsIterator query(final String sql, final boolean recycling) { EnumRows rows = null; synchronized (this.apiSync) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase == null) { return null; } else { try { rows = geodatabase.query(sql, recycling); } catch (final Throwable t) { throw new RuntimeException("Error running sql: " + sql, t); } finally { releaseGeodatabase(); } } } return new FileGdbEnumRowsIterator(this, rows); } @Override protected Map<PathName, ? extends RecordStoreSchemaElement> refreshSchemaElements( final RecordStoreSchema schema) { synchronized (this.apiSync) { synchronized (API_SYNC) { final Map<PathName, RecordStoreSchemaElement> elementsByPath = new TreeMap<>(); final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { final PathName schemaPath = schema.getPathName(); final String schemaCatalogPath = getCatalogPath(schema); final VectorOfWString childDatasets = getChildDatasets(geodatabase, schemaCatalogPath, "Feature Dataset"); if (childDatasets != null) { for (int i = 0; i < childDatasets.size(); i++) { final String childCatalogPath = childDatasets.get(i); final PathName childPath = toPath(childCatalogPath); RecordStoreSchema childSchema = schema.getSchema(childPath); if (childSchema == null) { childSchema = newFeatureDatasetSchema(schema, childPath); } else { if (childSchema.isInitialized()) { childSchema.refresh(); } } elementsByPath.put(childPath, childSchema); } } if (schemaPath.isParentOf(this.defaultSchemaPath) && !elementsByPath.containsKey(this.defaultSchemaPath)) { final SpatialReference spatialReference = getSpatialReference(getGeometryFactory()); final RecordStoreSchema childSchema = newSchema(this.defaultSchemaPath, spatialReference); elementsByPath.put(this.defaultSchemaPath, childSchema); } if (schema.equalPath(this.defaultSchemaPath)) { refreshSchemaRecordDefinitions(elementsByPath, schemaPath, "\\", "Feature Class"); refreshSchemaRecordDefinitions(elementsByPath, schemaPath, "\\", "Table"); } refreshSchemaRecordDefinitions(elementsByPath, schemaPath, schemaCatalogPath, "Feature Class"); refreshSchemaRecordDefinitions(elementsByPath, schemaPath, schemaCatalogPath, "Table"); } finally { releaseGeodatabase(); } } return elementsByPath; } } } private void refreshSchemaRecordDefinitions( final Map<PathName, RecordStoreSchemaElement> elementsByPath, final PathName schemaPath, final String catalogPath, final String datasetType) { synchronized (this.apiSync) { synchronized (API_SYNC) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { final boolean pathExists = isPathExists(geodatabase, catalogPath); if (pathExists) { final VectorOfWString childFeatureClasses = getChildDatasets(geodatabase, catalogPath, datasetType); if (childFeatureClasses != null) { for (int i = 0; i < childFeatureClasses.size(); i++) { final String childCatalogPath = childFeatureClasses.get(i); final String tableDefinition = geodatabase.getTableDefinition(childCatalogPath); final RecordDefinition recordDefinition = getRecordDefinition(schemaPath, childCatalogPath, tableDefinition); initRecordDefinition(recordDefinition); final PathName childPath = recordDefinition.getPathName(); elementsByPath.put(childPath, recordDefinition); } } } } finally { releaseGeodatabase(); } } } } } @Override protected void releaseConnected() { releaseGeodatabase(); } private void releaseGeodatabase() { synchronized (this.apiSync) { if (this.geodatabase != null) { this.geodatabaseReferenceCount--; if (this.geodatabaseReferenceCount <= 0) { this.geodatabaseReferenceCount = 0; try { closeGeodatabase(this.geodatabase); } finally { this.geodatabase = null; } } } } } protected void releaseTable(final String catalogPath) { synchronized (this.apiSync) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { final Table table = this.tableByCatalogPath.get(catalogPath); if (table != null) { final Integer count = Maps.decrementCount(this.tableReferenceCountsByCatalogPath, catalogPath); if (count == 0) { try { this.tableByCatalogPath.remove(catalogPath); this.tableWriteLockCountsByCatalogPath.remove(catalogPath); geodatabase.closeTable(table); } catch (final Exception e) { Logs.error(this, "Unable to close table: " + catalogPath, e); } finally { if (this.tableByCatalogPath.isEmpty()) { this.geodatabaseReferenceCount--; } table.delete(); } } } } finally { releaseGeodatabase(); } } } } protected void releaseTableAndWriteLock(final RecordDefinition recordDefinition) { final String catalogPath = getCatalogPath(recordDefinition); releaseTableAndWriteLock(catalogPath); } protected void releaseTableAndWriteLock(final String catalogPath) { synchronized (this.apiSync) { final Geodatabase geodatabase = getGeodatabase(); if (geodatabase != null) { try { final Table table = this.tableByCatalogPath.get(catalogPath); if (table != null) { final Integer count = Maps.decrementCount(this.tableWriteLockCountsByCatalogPath, catalogPath); if (count == 0) { try { table.setLoadOnlyMode(false); table.freeWriteLock(); } catch (final Exception e) { Logs.error(this, "Unable to free write lock for table: " + catalogPath, e); } } } releaseTable(catalogPath); } finally { releaseGeodatabase(); } } } } public FileGdbEnumRowsIterator search(final Object typePath, final Table table, final String fields, final String whereClause, final boolean recycling) { EnumRows rows = null; synchronized (this.apiSync) { if (isOpen(table)) { try { rows = table.search(fields, whereClause, recycling); } catch (final Throwable e) { if (!isClosed()) { final StringBuilder logQuery = new StringBuilder("ERROR executing query SELECT "); logQuery.append(fields); logQuery.append(" FROM "); logQuery.append(typePath); if (Property.hasValue(whereClause)) { logQuery.append(" WHERE "); logQuery.append(whereClause); } Exceptions.wrap(logQuery.toString(), e); } } } return new FileGdbEnumRowsIterator(this, rows); } } public FileGdbEnumRowsIterator search(final Object typePath, final Table table, final String fields, final String whereClause, final Envelope boundingBox, final boolean recycling) { EnumRows rows = null; if (!boundingBox.IsEmpty()) { synchronized (this.apiSync) { if (isOpen(table)) { try { rows = table.search(fields, whereClause, boundingBox, recycling); } catch (final Throwable e) { if (!isClosed()) { final StringBuilder logQuery = new StringBuilder("ERROR executing query SELECT "); logQuery.append(fields); logQuery.append(" FROM "); logQuery.append(typePath); logQuery.append(" WHERE "); if (Property.hasValue(whereClause)) { logQuery.append(whereClause); logQuery.append(" AND"); } logQuery.append("GEOMETRY intersects BBOX("); logQuery.append(boundingBox.getXMin()); logQuery.append(" "); logQuery.append(boundingBox.getXMax()); logQuery.append(","); logQuery.append(boundingBox.getYMin()); logQuery.append(" "); logQuery.append(boundingBox.getYMax()); logQuery.append(")"); Exceptions.wrap(logQuery.toString(), e); } } } } } return new FileGdbEnumRowsIterator(this, rows); } public void setCreateAreaField(final boolean createAreaField) { this.createAreaField = createAreaField; } public void setCreateLengthField(final boolean createLengthField) { this.createLengthField = createLengthField; } public void setCreateMissingRecordStore(final boolean createMissingRecordStore) { this.createMissingRecordStore = createMissingRecordStore; } public void setCreateMissingTables(final boolean createMissingTables) { this.createMissingTables = createMissingTables; } public void setDefaultSchema(final PathName defaultSchema) { synchronized (this.apiSync) { if (Property.hasValue(defaultSchema)) { this.defaultSchemaPath = defaultSchema; } else { this.defaultSchemaPath = PathName.ROOT; } } } public void setDefaultSchema(final String defaultSchema) { synchronized (this.apiSync) { if (Property.hasValue(defaultSchema)) { this.defaultSchemaPath = PathName.newPathName(defaultSchema); } else { this.defaultSchemaPath = PathName.ROOT; } } } public void setDomainFieldNames(final Map<String, List<String>> domainFieldNames) { this.domainFieldNames = domainFieldNames; } public void setFileName(final String fileName) { this.fileName = fileName; } public void setNull(final Row row, final String name) { synchronized (this.apiSync) { row.setNull(name); } } public String toCatalogPath(final PathName path) { return path.getPath().replaceAll("/", "\\\\"); } protected PathName toPath(final String catalogPath) { return PathName.newPathName(catalogPath); } @Override public String toString() { return this.fileName; } @Override public void updateRecord(final Record record) { if (record == null) { } else { final RecordDefinition recordDefinition = record.getRecordDefinition(); final Table table = getTableWithWriteLock(recordDefinition); try { updateRecord(table, record); } finally { releaseTableAndWriteLock(recordDefinition); } } } void updateRecord(final Table table, final Record record) { final Object objectId = record.getValue("OBJECTID"); if (objectId == null) { insertRecord(table, record); } else { final RecordDefinition sourceRecordDefinition = record.getRecordDefinition(); final RecordDefinition recordDefinition = getRecordDefinition(sourceRecordDefinition); validateRequired(record, recordDefinition); final PathName typePath = sourceRecordDefinition.getPathName(); final String whereClause = "OBJECTID=" + objectId; try ( final FileGdbEnumRowsIterator rows = search(typePath, table, "*", whereClause, false)) { for (final Row row : rows) { try { for (final FieldDefinition field : recordDefinition.getFields()) { final String name = field.getName(); try { final Object value = record.getValue(name); final AbstractFileGdbFieldDefinition esriField = (AbstractFileGdbFieldDefinition)field; esriField.setUpdateValue(record, row, value); } catch (final Throwable e) { throw new ObjectPropertyException(record, name, e); } } updateRow(typePath, table, row); record.setState(RecordState.PERSISTED); addStatistic("Update", record); } catch (final ObjectException e) { if (e.getObject() == record) { throw e; } else { throw new ObjectException(record, e); } } catch (final Throwable e) { throw new ObjectException(record, e); } } } } } protected void updateRow(final PathName typePath, final Table table, final Row row) { synchronized (this.apiSync) { if (isOpen(table)) { final boolean loadOnly = isTableLocked(typePath); if (loadOnly) { table.setLoadOnlyMode(false); } table.updateRow(row); if (loadOnly) { table.setLoadOnlyMode(true); } } } } private void validateRequired(final Record record, final RecordDefinition recordDefinition) { for (final FieldDefinition field : recordDefinition.getFields()) { final String name = field.getName(); if (field.isRequired()) { final Object value = record.getValue(name); if (value == null && !((AbstractFileGdbFieldDefinition)field).isAutoCalculated()) { throw new ObjectPropertyException(record, name, "Value required"); } } } } }