public class Db<T extends Db> extends Rule<T>
The goal of the Db abstraction is to allow Actions like DbGet/Put/Post/Patch/DeleteAction to apply the same REST CRUD operations agnostically across multiple backend data storage engines.
The primary job of a Db subclass is to:
#doStartup(Api).
select(Collection, Map), upsert(Collection, List), delete(Collection, List).
Actions such as DbGetAction then:
Rule.RuleMatcher| Modifier and Type | Field and Description |
|---|---|
protected boolean |
bootstrap
Indicates that this Db should reflectively create and configure Collections to represent its underlying tables.
|
protected java.util.ArrayList<Collection> |
collections
The Collections that are the REST interface to the backend tables (or buckets, folders, containers etc.) this Db exposes through an Api.
|
protected boolean |
dryRun
When set to true the Db will do everything it can to "work offline" logging commands it would have run but not actually running them.
|
protected java.util.Set<java.lang.String> |
excludeColumns
OPTIONAL column names that should be excluded from RQL queries, upserts and patches.
|
protected java.util.Set<java.lang.String> |
includeColumns
OPTIONAL column names that should be included in RQL queries, upserts and patches.
|
protected java.util.HashMap<java.lang.String,java.lang.String> |
includeTables
A tableName to collectionName map that can be used by whitelist backend tables that should be included in reflective Collection creation.
|
protected org.slf4j.Logger |
log |
protected static java.util.Set<java.lang.String> |
reservedParams
These params are specifically NOT passed to the Query for parsing.
|
protected java.lang.String |
type
A property that can be used to disambiguate different backends supported by a single subclass.
|
ALL_METHODS, configMap, description, excludeMatchers, excludeOn, includeMatchers, includeOn, name, order, params| Modifier and Type | Method and Description |
|---|---|
protected java.lang.String |
beautifyCollectionName(java.lang.String tableName)
Attempts to camelCase the table name to make it an attractive REST collection name.
|
protected java.lang.String |
beautifyName(java.lang.String name)
Try to make an attractive camelCase valid javascript variable name.
|
protected void |
buildCollections()
Creates a collection for every table name in
includeTables giving the Collections and Properties beautified JSON names. |
protected void |
buildRelationships()
Creates ONE_TO_MANY, MANY_TO_ONE, and MANY_TO_MANY Relationships with attractive RESTish names
based on the primary Index and foreign key Index structures of the Collections.
|
java.lang.Object |
castDbOutput(Property property,
java.lang.Object value)
Casts value as Property.type
|
java.lang.Object |
castJsonInput(Property property,
java.lang.Object value)
Casts value as Property.type.
|
static java.lang.Object |
castJsonInput(java.lang.String type,
java.lang.Object value) |
protected void |
configApi(Api api)
Does some final configuration adds all non excluded Collections to the Api via Api.withCollection
|
protected void |
configDb()
Subclasses should reflectively create Collections and Properties, Indexes, and Relationships here.
|
void |
delete(Collection collection,
java.util.List<java.util.Map<java.lang.String,java.lang.Object>> indexValues)
Deletes rows identified by the unique index values from the underlying data source.
|
void |
doDelete(Collection collection,
java.util.List<java.util.Map<java.lang.String,java.lang.Object>> indexValues) |
java.util.List<java.lang.String> |
doPatch(Collection collection,
java.util.List<java.util.Map<java.lang.String,java.lang.Object>> rows) |
Results |
doSelect(Collection collection,
java.util.List<Term> queryTerms)
Finds all records that match the supplied RQL query terms.
|
protected void |
doShutdown() |
protected void |
doShutdown(Api api)
Made to be overridden by subclasses or anonymous inner classes to do specific cleanup
|
protected void |
doStartup(Api api)
Made to be overridden by subclasses or anonymous inner classes to do specific init of an Api.
|
java.util.List<java.lang.String> |
doUpsert(Collection collection,
java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records)
Upserts the key/values pairs for each record into the underlying data source.
|
protected boolean |
excludeTable(java.lang.String tableName) |
boolean |
filterOutJsonProperty(Collection collection,
java.lang.String name)
Checks if "collectionName.columnName" or just "columnName" is specifically included or excluded via
includeColumns
excludeColumns or is a valid Property columnName. |
Collection |
getCollection(java.lang.String collectionOrTableName) |
Collection |
getCollectionByTableName(java.lang.String tableName) |
java.util.List<Collection> |
getCollections() |
protected Property |
getProperty(java.lang.String tableName,
java.lang.String columnName) |
java.lang.String |
getType() |
boolean |
isBootstrap() |
boolean |
isDryRun() |
boolean |
isRunning(Api api) |
boolean |
isType(java.lang.String... types) |
protected java.lang.String |
makeRelationshipName(Collection collection,
Relationship relationship)
Attempts to construct a sensible json property name for a Relationship.
|
protected java.util.List<java.util.Map<java.lang.String,java.lang.Object>> |
mapToColumnNames(Collection collection,
java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records) |
protected java.util.Set<Term> |
mapToColumnNames(Collection collection,
Term term) |
protected void |
mapToJsonNames(Collection collection,
Term term) |
java.util.List<java.lang.String> |
patch(Collection collection,
java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records)
Should be called by Actions instead of upsert() only when all records are strictly known to exist.
|
void |
removeCollection(Collection table) |
Results |
select(Collection collection,
java.util.Map<java.lang.String,java.lang.String> params)
Finds all records that match the supplied RQL query terms.
|
T |
shutdown()
Shutsdown all running Apis.
|
T |
shutdown(Api api) |
T |
startup(Api api)
Called by an Api to as part of Api.startup().
|
java.util.List<java.lang.String> |
upsert(Collection collection,
java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records) |
T |
withBootstrap(boolean bootstrap) |
T |
withCollection(Collection collection) |
T |
withCollections(Collection... collections) |
T |
withDryRun(boolean dryRun) |
T |
withExcludeColumns(java.lang.String... columnNames) |
T |
withIncludeColumns(java.lang.String... columnNames) |
T |
withIncludeTable(java.lang.String tableName,
java.lang.String collectionName)
Whitelists tableName as an underlying table that should be reflectively bootstrapped and exposed as collectionName.
|
T |
withIncludeTables(java.lang.String... includeTables)
Utility that parses a comma and pipe separated list of table name to collection name mappings.
|
T |
withType(java.lang.String type) |
afterWiringComplete, checkLazyConfig, compareTo, doLazyConfig, getAllExcludePaths, getAllIncludeMethods, getAllIncludePaths, getDefaultIncludeMatchers, getDescription, getExcludeMatchers, getIncludeMatchers, getName, getOrder, getParams, match, match, matches, matches, toString, withDescription, withExcludeOn, withExcludeOn, withIncludeOn, withIncludeOn, withName, withOrder, withParam, withParamsprotected static final java.util.Set<java.lang.String> reservedParams
protected final org.slf4j.Logger log
protected final java.util.ArrayList<Collection> collections
protected final java.util.HashMap<java.lang.String,java.lang.String> includeTables
protected final java.util.Set<java.lang.String> includeColumns
protected final java.util.Set<java.lang.String> excludeColumns
protected boolean bootstrap
This would be false when an Api designer wants to very specifically configure an Api probably when the underlying db does not support the type of reflection required. For example, you may want to put specific Property and Relationship structure on top of an unstructured JSON document store.
protected java.lang.String type
For example type might be "mysql" for a JdbcDb.
protected boolean dryRun
protected boolean excludeTable(java.lang.String tableName)
public static java.lang.Object castJsonInput(java.lang.String type,
java.lang.Object value)
public final T startup(Api api)
This implementation really only manages starting/started state, with the heaving lifting of bootstrapping delegated to doStartup(Api).
api - the api to startdoStartup(Api)protected void doStartup(Api api)
This method will not be called a second time after for an Api unless the Api is shutdown and then restarted.
The default implementation, when isBootstrap() is true, calls configDb() once globally and configApi(Api) once for each Api passed in.
api - the api to startconfigDb(),
configApi(Api)public T shutdown()
This is primarily a method used for testing.
protected void doShutdown()
protected void doShutdown(Api api)
api - the api shutting downpublic boolean isRunning(Api api)
public final Results select(Collection collection, java.util.Map<java.lang.String,java.lang.String> params) throws ApiException
The implementation of this method primarily translates jsonNames to columnNames for RQL inputs and JSON outputs
delegating the work to doSelect(Collection, List) where all ins and outs are based on columnName.
collection - the collection being queriedparams - RQL terms that have been translated to use Property jsonNamesApiException - TODO: update/correct this javadocpublic Results doSelect(Collection collection, java.util.List<Term> queryTerms) throws ApiException
collection - the collection to queryqueryTerms - RQL terms that have been translated to use Property columnNames not jsonNamesApiExceptionpublic final java.util.List<java.lang.String> upsert(Collection collection, java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records) throws ApiException
ApiExceptionpublic java.util.List<java.lang.String> doUpsert(Collection collection, java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records) throws ApiException
Keys that are not supplied in the call but that exist in the row in the target DB should not be modified.
Each row should minimally contain key value pairs that satisfy one of the tables unique index constraints allowing an update to take place instead of an insert if the row already exists in the underlying data source.
IMPORTANT #1 - implementors should note that the keys on each record may be different.
IMPORTANT #2 - strict POST/PUT vs POST/PATCH semantics are implementation specific. For example, a RDBMS backed implementation may choose to upsert only the supplied client supplied keys effectively making this a POST/PATCH operation. A document store that is simply storing the supplied JSON may not be able to do partial updates elegantly and replace existing documents entirely rendering this a POST/PUT.
collection - the collection being modifiedrecords - the records being modifiedApiExceptionpublic java.util.List<java.lang.String> patch(Collection collection, java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records) throws ApiException
The default implementation simply calls upsert().
collection - the collection to patchrecords - the key/value pairs to update on existing recordsApiExceptionpublic java.util.List<java.lang.String> doPatch(Collection collection, java.util.List<java.util.Map<java.lang.String,java.lang.Object>> rows) throws ApiException
ApiExceptionpublic final void delete(Collection collection, java.util.List<java.util.Map<java.lang.String,java.lang.Object>> indexValues) throws ApiException
IMPORTANT implementors should note that the keys on each indexValues row may be different.
The keys should have come from a unique index, meaning that the key/value pairs for each row should uniquely identify the row, however there is no guarantee that each row will reference the same index.
collection - the collection being modifiedindexValues - the identifiers for the records to deleteApiExceptionpublic void doDelete(Collection collection, java.util.List<java.util.Map<java.lang.String,java.lang.Object>> indexValues) throws ApiException
ApiExceptionprotected void configApi(Api api)
api - the Api to configure.protected void configDb()
throws ApiException
The default implementation simply delegates to buildCollections() and buildRelationships().
Generally, you will need to override buildCollections when implementing a new Db subclass but can probably leave buildRelationships alone as all it does is reflectively build Relationships off of Indexes that are on the Collections.
ApiException - when configuration failsprotected void buildCollections()
includeTables giving the Collections and Properties beautified JSON names.
Subclasses should override this method to reflectively create Collections, Properties and Indexes and then call super.buildCollections() if they want names them beautified.
protected void buildRelationships()
For all foreign key indexes, two relationships objects are created representing both sides of the relationship. A MANY_TO_ONE also creates a ONE_TO_MANY and vice versa and there are always two for a MANY_TO_MANY modeling the relationship from both sides.
protected java.lang.String beautifyCollectionName(java.lang.String tableName)
includeTables
contains tableName as a key, the value from includeTables is returned as is.tableName - the name of an underlying datasource table to be turned into a pretty REST collection namebeautifyName(String)protected java.lang.String beautifyName(java.lang.String name)
Lots of sql db designers use things like SNAKE_CASE_COLUMN_NAMES that look terrible as json property names.
name - the to beautifynameprotected java.lang.String makeRelationshipName(Collection collection, Relationship relationship)
For example, a "ONE Author TO_MANY Books" relationship might show up as "books" on the Author collection and "Author" on the Books collection.
The algorithm attempts to pluralize the "MANY" side of the relationship.
The "ONE" side of a relationship uses the first foreign key column name minus an trailing case insensitive "ID", so in the above example, if there is a foreign key Book.authorId pointing to the Author table, the the name would be "author". If the foreign key column name was Book.primaryAuthorKey, the relationship would be named "primaryAuthorKey".
collection - the collection the relationship name being created will belong torelationship - the relationshippublic java.lang.Object castDbOutput(Property property, java.lang.Object value)
property - the property the value is assigned tovalue - the value pulled from the DBvalue cast to Property.typepublic java.lang.Object castJsonInput(Property property, java.lang.Object value)
property - the property the value is assigned tovalue - the value to cast to the datatype of propertyvalue cast to Property.typecastJsonInput(String, Object)protected void mapToJsonNames(Collection collection, Term term)
protected java.util.Set<Term> mapToColumnNames(Collection collection, Term term)
protected java.util.List<java.util.Map<java.lang.String,java.lang.Object>> mapToColumnNames(Collection collection, java.util.List<java.util.Map<java.lang.String,java.lang.Object>> records)
protected Property getProperty(java.lang.String tableName, java.lang.String columnName)
public Collection getCollection(java.lang.String collectionOrTableName)
public Collection getCollectionByTableName(java.lang.String tableName)
public void removeCollection(Collection table)
public java.util.List<Collection> getCollections()
collectionspublic T withIncludeTables(java.lang.String... includeTables)
Example: db.tables=customers,books-prod-catalog|books,INVENTORY_ADJUSTED_BY_PERIOD|inventory
This method is primarily useful when an Api designer wants to manually configure Collections instead of having the Db reflectively build the Collections.
This method can also be called prior to the db reflection phase of startup to whitelist tables that the reflection should include.
The string is first split on "," to get a list of table names.
If the table name contains a "|" character, the part on the left is considered the tableName and the part on the right is considered the collectionName.
If there is no "|" then the beautified tableName is used for the collectionName.
includeTables - underlying data source tables that should be included as REST collections
TODO: this should lazy init off a string config property
public T withIncludeTable(java.lang.String tableName, java.lang.String collectionName)
tableName - the table to build a Collection forcollectionName - the name of the target collection that can be different from tableName.public T withCollections(Collection... collections)
collections - to include (add not replace)public T withCollection(Collection collection)
public boolean filterOutJsonProperty(Collection collection, java.lang.String name)
includeColumns
excludeColumns or is a valid Property columnName.
This can be used to filter out things like URL path mapped parameters that don't actually match to "columns" in a document store.
This does not prevent the underlying Property from being part of a Collection object model and the names here don't actually have to be Properties.
collection - the collection in questionname - the name of the property to optionally filterpublic T withIncludeColumns(java.lang.String... columnNames)
public T withExcludeColumns(java.lang.String... columnNames)
public boolean isType(java.lang.String... types)
public java.lang.String getType()
public T withType(java.lang.String type)
public boolean isBootstrap()
public T withBootstrap(boolean bootstrap)
public boolean isDryRun()
public T withDryRun(boolean dryRun)
Copyright © 2023 Rocket Partners, LLC. All rights reserved.