package com.revolsys.record.schema; import java.io.Closeable; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.springframework.beans.DirectFieldAccessor; import org.springframework.transaction.PlatformTransactionManager; import com.revolsys.collection.ListResultPager; import com.revolsys.collection.ResultPager; import com.revolsys.collection.iterator.AbstractIterator; import com.revolsys.collection.map.MapEx; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.GeometryFactoryProxy; import com.revolsys.identifier.Identifier; import com.revolsys.io.FileUtil; import com.revolsys.io.IoFactory; import com.revolsys.io.PathName; import com.revolsys.io.map.MapObjectFactoryRegistry; import com.revolsys.jdbc.io.RecordStoreIteratorFactory; import com.revolsys.properties.ObjectWithProperties; import com.revolsys.record.ArrayRecord; import com.revolsys.record.Record; import com.revolsys.record.RecordFactory; import com.revolsys.record.code.CodeTable; import com.revolsys.record.code.CodeTableProperty; import com.revolsys.record.io.ListRecordReader; import com.revolsys.record.io.RecordReader; import com.revolsys.record.io.RecordStoreConnection; import com.revolsys.record.io.RecordStoreFactory; import com.revolsys.record.io.RecordStoreQueryReader; import com.revolsys.record.io.RecordWriter; import com.revolsys.record.query.Q; import com.revolsys.record.query.Query; import com.revolsys.record.query.QueryValue; import com.revolsys.transaction.Transactionable; import com.revolsys.util.Property; import com.revolsys.util.count.CategoryLabelCountMap; import com.revolsys.util.count.LabelCountMap; public interface RecordStore extends GeometryFactoryProxy, RecordDefinitionFactory, Transactionable, Closeable, ObjectWithProperties { static boolean isRecordStore(final Path path) { for (final RecordStoreFactory recordStoreFactory : IoFactory .factories(RecordStoreFactory.class)) { if (recordStoreFactory.canOpenPath(path)) { return true; } } return false; } @SuppressWarnings("unchecked") static void mapObjectFactoryInit() { MapObjectFactoryRegistry.newFactory("recordStore", (final Map<String, ? extends Object> config) -> { final Map<String, Object> connectionProperties = (Map<String, Object>)config .get("connection"); if (Property.isEmpty(connectionProperties)) { throw new IllegalArgumentException( "Record store must include a 'connection' map property: " + config); } else { final RecordStore recordStore = RecordStore.newRecordStore(connectionProperties); recordStore.setProperties(config); recordStore.initialize(); return recordStore; } }); MapObjectFactoryRegistry.newFactory("codeTable", CodeTableProperty::new); } static <T extends RecordStore> T newRecordStore(final File file) { return newRecordStore(FileUtil.toUrlString(file)); } static <T extends RecordStore> T newRecordStore(final File directory, final String fileExtension) { if (!directory.exists()) { throw new IllegalArgumentException("Directory does not exist: " + directory); } else if (!directory.isDirectory()) { throw new IllegalArgumentException("File is not a directory: " + directory); } else { final String url = FileUtil.toUrlString(directory) + "?format=" + fileExtension; return newRecordStore(url); } } /** * Construct a newn initialized record store. * @param connectionProperties * @return */ @SuppressWarnings("unchecked") static <T extends RecordStore> T newRecordStore( final Map<String, ? extends Object> connectionProperties) { final String url = (String)connectionProperties.get("url"); final RecordStoreFactory factory = recordStoreFactory(url); if (factory == null) { throw new IllegalArgumentException("Record Store Factory not found for " + url); } else { return (T)factory.newRecordStore(connectionProperties); } } @SuppressWarnings("unchecked") static <T extends RecordStore> T newRecordStore(final String url) { final RecordStoreFactory factory = recordStoreFactory(url); if (factory == null) { throw new IllegalArgumentException("Record Store Factory not found for " + url); } else { final Map<String, Object> connectionProperties = new HashMap<>(); connectionProperties.put("url", url); return (T)factory.newRecordStore(connectionProperties); } } @SuppressWarnings("unchecked") static <T extends RecordStore> T newRecordStore(final String url, final String user, final String password) { final RecordStoreFactory factory = recordStoreFactory(url); if (factory == null) { throw new IllegalArgumentException("Record Store Factory not found for " + url); } else { final Map<String, Object> connectionProperties = new HashMap<>(); connectionProperties.put("url", url); connectionProperties.put("user", user); connectionProperties.put("password", password); return (T)factory.newRecordStore(connectionProperties); } } static RecordStoreFactory recordStoreFactory(final String url) { if (url == null) { throw new IllegalArgumentException("The url parameter must be specified"); } else { for (final RecordStoreFactory factory : IoFactory.factories(RecordStoreFactory.class)) { if (factory.canOpenUrl(url)) { return factory; } } return null; } } static Class<?> recordStoreInterfaceClass( final Map<String, ? extends Object> connectionProperties) { final String url = (String)connectionProperties.get("url"); final RecordStoreFactory factory = recordStoreFactory(url); if (factory == null) { throw new IllegalArgumentException("Data Source Factory not found for " + url); } else { return factory.getRecordStoreInterfaceClass(connectionProperties); } } static void setConnectionProperties(final RecordStore recordStore, final Map<String, Object> properties) { if (recordStore != null) { final DirectFieldAccessor dataSourceBean = new DirectFieldAccessor(recordStore); for (final Entry<String, Object> property : properties.entrySet()) { final String name = property.getKey(); final Object value = property.getValue(); try { dataSourceBean.setPropertyValue(name, value); } catch (final Throwable e) { } } } } void addCodeTable(CodeTable codeTable); default void addCodeTables(final Collection<CodeTable> codeTables) { for (final CodeTable codeTable : codeTables) { addCodeTable(codeTable); } } default void addStatistic(final String statisticName, final Record object) { if (getStatistics() != null) { getStatistics().addCount(statisticName, object); } } default void addStatistic(final String statisticName, final String typePath, final int count) { if (getStatistics() != null) { getStatistics().addCount(statisticName, typePath, count); } } default void appendQueryValue(final Query query, final StringBuilder sql, final QueryValue queryValue) { queryValue.appendDefaultSql(query, this, sql); } @Override void close(); default boolean deleteRecord(final PathName typePath, final Identifier identifier) { final RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition != null) { final String idFieldName = recordDefinition.getIdFieldName(); if (idFieldName != null) { final Query query = Query.equal(recordDefinition, idFieldName, identifier); if (deleteRecords(query) == 1) { return true; } } } return false; } default boolean deleteRecord(final Record record) { throw new UnsupportedOperationException("Delete not supported"); } default int deleteRecords(final Iterable<? extends Record> records) { int count = 0; for (final Record record : records) { if (deleteRecord(record)) { count++; } } return count; } default int deleteRecords(final Query query) { int count = 0; try ( final RecordReader reader = getRecords(query)) { for (final Record record : reader) { if (deleteRecord(record)) { count++; } } } return count; } default RecordDefinition findRecordDefinition(final PathName typePath) { final PathName schemaName = typePath.getParent(); final RecordStoreSchema schema = getSchema(schemaName); if (schema == null) { return null; } else { return schema.findRecordDefinition(typePath); } } @SuppressWarnings("unchecked") default <V extends CodeTable> V getCodeTable(final PathName typePath) { final RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { return null; } else { final CodeTableProperty codeTable = CodeTableProperty.getProperty(recordDefinition); return (V)codeTable; } } default <V extends CodeTable> V getCodeTable(final String typePath) { final PathName pathName = PathName.newPathName(typePath); return getCodeTable(pathName); } CodeTable getCodeTableByFieldName(CharSequence fieldName); Map<String, CodeTable> getCodeTableByFieldNameMap(); RecordStoreConnected getConnected(); MapEx getConnectionProperties(); String getConnectionTitle(); RecordStoreIteratorFactory getIteratorFactory(); String getLabel(); default Record getRecord(final PathName typePath, final Identifier id) { final RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null || id == null) { return null; } else { final List<Object> values = id.getValues(); final List<String> idFieldNames = recordDefinition.getIdFieldNames(); if (idFieldNames.isEmpty()) { throw new IllegalArgumentException(typePath + " does not have a primary key"); } else if (values.size() != idFieldNames.size()) { throw new IllegalArgumentException( id + " not a valid id for " + typePath + " requires " + idFieldNames); } else { final Query query = new Query(recordDefinition); for (int i = 0; i < idFieldNames.size(); i++) { final String name = idFieldNames.get(i); final Object value = values.get(i); final FieldDefinition field = recordDefinition.getField(name); query.and(Q.equal(field, value)); } final RecordReader records = getRecords(query); return records.getFirst(); } } } default Record getRecord(final PathName typePath, final Object... id) { final Identifier identifier = Identifier.newIdentifier(id); return getRecord(typePath, identifier); } int getRecordCount(Query query); default RecordDefinition getRecordDefinition(final PathName path) { if (path == null) { return null; } else { final PathName schemaPath = path.getParent(); final RecordStoreSchema schema = getSchema(schemaPath); if (schema == null) { return null; } else { return schema.getRecordDefinition(path); } } } default RecordDefinition getRecordDefinition(final RecordDefinition objectRecordDefinition) { final String typePath = objectRecordDefinition.getPath(); final RecordDefinition recordDefinition = getRecordDefinition(typePath); return recordDefinition; } @Override default RecordDefinition getRecordDefinition(final String path) { return getRecordDefinition(PathName.newPathName(path)); } default List<RecordDefinition> getRecordDefinitions(final PathName path) { final RecordStoreSchema schema = getSchema(path); if (schema == null) { return Collections.emptyList(); } else { return schema.getRecordDefinitions(); } } RecordFactory<Record> getRecordFactory(); default RecordReader getRecords(final Collection<Query> queries) { final RecordStoreQueryReader reader = newRecordReader(); for (final Query query : queries) { if (query != null) { reader.addQuery(query); } } return reader; } default RecordReader getRecords(final PathName path) { final RecordStoreSchemaElement element = getRootSchema().getElement(path); if (element instanceof RecordDefinition) { final RecordDefinition recordDefinition = (RecordDefinition)element; final Query query = new Query(recordDefinition); return getRecords(query); } else if (element instanceof RecordStoreSchema) { final RecordStoreSchema schema = (RecordStoreSchema)element; final List<Query> queries = new ArrayList<>(); for (final RecordDefinition recordDefinition : schema.getRecordDefinitions()) { final Query query = new Query(recordDefinition); queries.add(query); } return getRecords(queries); } else { return new ListRecordReader(null, Collections.emptyList()); } } default RecordReader getRecords(final Query query) { final RecordStoreQueryReader reader = newRecordReader(); reader.addQuery(query); return reader; } RecordStoreConnection getRecordStoreConnection(); String getRecordStoreType(); RecordStoreSchema getRootSchema(); default RecordStoreSchema getSchema(final PathName pathName) { final RecordStoreSchema rootSchema = getRootSchema(); return rootSchema.getSchema(pathName); } default RecordStoreSchema getSchema(final String path) { return getSchema(PathName.newPathName(path)); } CategoryLabelCountMap getStatistics(); default LabelCountMap getStatistics(final String name) { final CategoryLabelCountMap statistics = getStatistics(); return statistics.getLabelCountMap(name); } @Override default PlatformTransactionManager getTransactionManager() { return null; } String getUrl(); String getUsername(); default boolean hasSchema(final PathName schemaName) { return getSchema(schemaName) != null; } void initialize(); default Record insertRecord(final PathName pathName, final Object... values) { final RecordDefinition recordDefinition = getRecordDefinition(pathName); final Record record = new ArrayRecord(recordDefinition, values); insertRecord(record); return record; } default void insertRecord(final Record record) { throw new UnsupportedOperationException("Insert not supported"); } default void insertRecords(final Iterable<? extends Record> records) { for (final Record record : records) { insertRecord(record); } } default boolean isClosed() { return false; } default boolean isEditable(final PathName typePath) { return false; } boolean isLoadFullSchema(); default AbstractIterator<Record> newIterator(final Query query, Map<String, Object> properties) { if (properties == null) { properties = Collections.emptyMap(); } if (query == null) { return null; } else { final RecordDefinition recordDefinition = query.getRecordDefinition(); if (recordDefinition != null) { final RecordStoreIteratorFactory recordStoreIteratorFactory = recordDefinition .getProperty("recordStoreIteratorFactory"); if (recordStoreIteratorFactory != null) { final AbstractIterator<Record> iterator = recordStoreIteratorFactory.newIterator(this, query, properties); if (iterator != null) { return iterator; } } } final RecordStoreIteratorFactory iteratorFactory = getIteratorFactory(); return iteratorFactory.newIterator(this, query, properties); } } default Identifier newPrimaryIdentifier(final PathName typePath) { return null; } default Query newQuery(final String typePath, final String whereClause, final BoundingBox boundingBox) { throw new UnsupportedOperationException(); } default Record newRecord(final PathName typePath) { final RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { return null; } else { return newRecord(recordDefinition); } } default Record newRecord(final PathName typePath, final Map<String, ? extends Object> values) { final RecordDefinition recordDefinition = getRecordDefinition(typePath); if (recordDefinition == null) { throw new IllegalArgumentException("Cannot find table " + typePath + " for " + this); } else { final Record record = newRecord(recordDefinition); if (record != null) { record.setValues(values); final String idFieldName = recordDefinition.getIdFieldName(); if (Property.hasValue(idFieldName)) { if (values.get(idFieldName) == null) { final Identifier id = newPrimaryIdentifier(typePath); record.setIdentifier(id); } } } return record; } } default Record newRecord(final Record record) { final RecordDefinition recordDefinition = record.getRecordDefinition(); final RecordDefinition recordStoreRecordDefinition = getRecordDefinition(recordDefinition); final RecordFactory<Record> recordFactory = getRecordFactory(); if (recordStoreRecordDefinition == null || recordFactory == null) { return null; } else { final Record copy = recordFactory.newRecord(recordStoreRecordDefinition); copy.setValuesClone(record); copy.setIdentifier(null); return copy; } } default Record newRecord(final RecordDefinition objectRecordDefinition) { final RecordDefinition recordDefinition = getRecordDefinition(objectRecordDefinition); final RecordFactory<Record> recordFactory = getRecordFactory(); if (recordDefinition == null || recordFactory == null) { return null; } else { final Record record = recordFactory.newRecord(recordDefinition); return record; } } default Record newRecord(RecordDefinition recordDefinition, final Map<String, ? extends Object> values) { final PathName typePath = recordDefinition.getPathName(); recordDefinition = getRecordDefinition(recordDefinition); if (recordDefinition == null) { throw new IllegalArgumentException("Cannot find table " + typePath + " for " + this); } else { final Record record = newRecord(recordDefinition); if (record != null) { record.setValues(values); final String idFieldName = recordDefinition.getIdFieldName(); if (Property.hasValue(idFieldName)) { if (values.get(idFieldName) == null) { final Identifier id = newPrimaryIdentifier(typePath); record.setIdentifier(id); } } } return record; } } default RecordStoreQueryReader newRecordReader() { final RecordStoreQueryReader reader = new RecordStoreQueryReader(this); return reader; } default Record newRecordWithIdentifier(final RecordDefinition recordDefinition) { final Record record = newRecord(recordDefinition); if (record != null) { final String idFieldName = recordDefinition.getIdFieldName(); if (Property.hasValue(idFieldName)) { final PathName typePath = recordDefinition.getPathName(); final Identifier id = newPrimaryIdentifier(typePath); record.setIdentifier(id); } } return record; } RecordWriter newRecordWriter(); default RecordWriter newRecordWriter(final RecordDefinition recordDefinition) { return newRecordWriter(); } default ResultPager<Record> page(final Query query) { final RecordReader results = getRecords(query); final List<Record> list = results.toList(); return new ListResultPager<>(list); } void setLabel(String label); void setLoadFullSchema(boolean loadFullSchema); void setRecordFactory(RecordFactory<? extends Record> recordFactory); void setRecordStoreConnection(RecordStoreConnection connection); default void setStatistics(final String name, final LabelCountMap labelCountMap) { final CategoryLabelCountMap categoryLabelCountMap = getStatistics(); categoryLabelCountMap.setLabelCountMap(name, labelCountMap); } default void updateRecord(final Record record) { throw new UnsupportedOperationException("Update not supported"); } default void updateRecords(final Iterable<? extends Record> records) { for (final Record record : records) { updateRecord(record); } } }