Class Db<T extends Db>
The goal of the Db abstraction is to allow Actions like RestGet/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:
- reflectively generate Collections to represent their underlying tables (or buckets, folders, containers etc.) columns, indexes, and relationships, during
#doStartup(Api). - implement REST CRUD support by implementing
select(Collection, Map),upsert(Collection, List),delete(Collection, List).
Actions such as DbGetAction then:
- translate a REST collection/resource request into columnName based json and RQL,
- execute the requested CRUD method on the Collection's underlying Db
- then translate the results back from the Db columnName based data into the approperiate jsonName version for external consumption.
-
Field Summary
FieldsModifier and TypeFieldDescriptionprotected booleanIndicates that this Db should reflectively create and configure Collections to represent its underlying tables.protected final ArrayList<Collection>The Collections that are the REST interface to the backend tables (or buckets, folders, containers etc.) this Db exposes through an Api.protected booleanWhen 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 PathUsed to differentiate which Collection is being referred by a Request when an Api supports Collections with the same name from different Dbs.OPTIONAL column names that should be excluded from RQL queries, upserts and patches.OPTIONAL column names that should be included in RQL queries, upserts and patches.A tableName to collectionName map that can be used by whitelist backend tables that should be included in reflicitive Collection creation.protected final org.slf4j.Loggerprotected StringThe name of his Db used for "name.property" style autowiring.These params are specifically NOT passed to the Query for parsing.protected StringA property that can be used to disambiguate different backends supported by a single subclass. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected StringbeautifyCollectionName(String tableName) Attempts to camelCase and pluralize the table name to make it an attractive REST collection name.protected StringbeautifyName(String name) Try to make an attractive camelCase valid javascript variable name.protected voidCreates a collection for every table name inincludeTablesgiving the Collections and Properties beautified JSON names.protected voidCreates 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.castDbOutput(Property property, Object value) Casts value as Property.typecastJsonInput(Property property, Object value) Casts value as Property.type.castJsonInput(String type, Object value) Casts value to as type.protected voidAdds all non excluded Collections to the Api via Api.withCollectionprotected voidconfigDb()Subclasses should reflectively create Collections and Properties, Indexes, and Relationships here.abstract voidDeletes rows identified by the unique index values from the underlying data source.voidabstract ResultsdoSelect(Collection collection, List<Term> queryTerms) Finds all records that match the supplied RQL query terms.protected voidprotected voiddoShutdown(Api api) Made to be overridden by subclasses or anonymous inner classes to do specific cleanupprotected voidMade to be overridden by subclasses or anonymous inner classes to do specific init of an Api.Upserts the key/values pairs for each record into the underlying data source.booleanfilterOutJsonProperty(Collection collection, String name) Checks if "collectionName.columnName" or just "columnName" is specifically included or excluded viaincludeColumnsexcludeColumnsor is a valid Property columnName.getCollectionByTableName(String tableName) getKey(Collection collection, Object node) getName()protected PropertygetProperty(String tableName, String columnName) getType()booleanbooleanisDryRun()booleanbooleanprotected StringmakeRelationshipName(Collection collection, Relationship relationship) Attempts to construct a sensible json property name for a Relationship.mapToColumnNames(Collection collection, Term term) protected voidmapToJsonNames(Collection collection, Term term) Should be called by Actions instead of upsert() only when all records are strictly known to exist.voidremoveCollection(Collection table) final Resultsselect(Collection collection, Map<String, String> params) Finds all records that match the supplied RQL query terms.shutdown()Shutsdown all running Apis.final TCalled by an Api to as part of Api.startup().withBootstrap(boolean bootstrap) withCollection(Collection collection) withCollections(Collection... collections) withDryRun(boolean dryRun) withEndpointPath(Path endpointPath) withExcludeColumns(String... columnNames) withIncludeColumns(String... columnNames) withIncludeTable(String tableName, String collectionName) Whitelists tableName as an underlying table that should be reflectively bootstrapped and exposed as collectionName.withIncludeTables(String includeTables) Utility that parses a comma and pipe separated list of table name to collection name mappings.The name of the Db is used primarily for autowiring "name.property" bean props from
-
Field Details
-
reservedParams
These params are specifically NOT passed to the Query for parsing. These are either dirty worlds like sql injection tokens or the are used by actions themselves -
log
protected final org.slf4j.Logger log -
collections
The Collections that are the REST interface to the backend tables (or buckets, folders, containers etc.) this Db exposes through an Api. -
includeTables
A tableName to collectionName map that can be used by whitelist backend tables that should be included in reflicitive Collection creation. -
includeColumns
OPTIONAL column names that should be included in RQL queries, upserts and patches. -
excludeColumns
OPTIONAL column names that should be excluded from RQL queries, upserts and patches. -
bootstrap
protected boolean bootstrapIndicates that this Db should reflectively create and configure Collections to represent its underlying tables.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.
-
name
The name of his Db used for "name.property" style autowiring. -
type
A property that can be used to disambiguate different backends supported by a single subclass.For example type might be "mysql" for a JdbcDb.
-
endpointPath
Used to differentiate which Collection is being referred by a Request when an Api supports Collections with the same name from different Dbs. -
dryRun
protected boolean dryRunWhen set to true the Db will do everything it can to "work offline" logging commands it would have run but not actually running them.
-
-
Constructor Details
-
Db
public Db() -
Db
-
-
Method Details
-
startup
Called by an Api to as part of Api.startup().This implementation really only manages starting/started state, with the heaving lifting of bootstrapping delegated to
doStartup(Api).- Parameters:
api- the api to start- Returns:
- this
- See Also:
-
doStartup
Made to be overridden by subclasses or anonymous inner classes to do specific init of an 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, callsconfigDb()once globally andconfigApi(Api)once for each Api passed in.- Parameters:
api- the api to start- See Also:
-
shutdown
Shutsdown all running Apis.This is primarily a method used for testing.
- Returns:
- this
-
doShutdown
protected void doShutdown() -
shutdown
-
doShutdown
Made to be overridden by subclasses or anonymous inner classes to do specific cleanup- Parameters:
api- the api shutting down
-
isRunning
-
select
Finds all records that match the supplied RQL query terms.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.- Parameters:
collection- the collection being queriedparams- RQL terms that have been translated to use Property jsonNames- Returns:
- A list of maps with keys as Property jsonNames
- Throws:
ApiException- TODO: update/correct this javadoc
-
doSelect
Finds all records that match the supplied RQL query terms.- Parameters:
collection- the collection to queryqueryTerms- RQL terms that have been translated to use Property columnNames not jsonNames- Returns:
- A list of maps with keys as Property columnNames not jsonNames
- Throws:
ApiException
-
upsert
public final List<String> upsert(Collection collection, List<Map<String, Object>> rows) throws ApiException- Throws:
ApiException
-
getHref
-
getKey
-
mapTo
-
doUpsert
public abstract List<String> doUpsert(Collection collection, List<Map<String, Object>> records) throws ApiExceptionUpserts the key/values pairs for each record into the underlying data source.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.
- Parameters:
collection- the collection being modifiedrecords- the records being modified- Returns:
- the encoded resource key for every supplied row
- Throws:
ApiException
-
patch
public List<String> patch(Collection collection, List<Map<String, Object>> records) throws ApiExceptionShould be called by Actions instead of upsert() only when all records are strictly known to exist.The default implementation simply calls upsert().
- Parameters:
collection- the collection to patchrecords- the key/value pairs to update on existing records- Returns:
- the keys of all resources modified
- Throws:
ApiException
-
doPatch
- Throws:
ApiException
-
delete
public abstract void delete(Collection collection, List<Map<String, Object>> indexValues) throws ApiExceptionDeletes rows identified by the unique index values from the underlying data source.IMPORTANT implementors should note that the keys on each
indexValuesrow 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.
- Parameters:
collection- the collection being modifiedindexValues- the identifiers for the records to delete- Throws:
ApiException
-
configApi
Adds all non excluded Collections to the Api via Api.withCollection- Parameters:
api- the Api to configure.
-
configDb
Subclasses should reflectively create Collections and Properties, Indexes, and Relationships here.The default implementation simply delegates to
buildCollections()andbuildRelationships().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.
- Throws:
ApiException- when configuration fails
-
buildCollections
protected void buildCollections()Creates a collection for every table name inincludeTablesgiving 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.
-
buildRelationships
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.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.
-
beautifyCollectionName
Attempts to camelCase and pluralize the table name to make it an attractive REST collection name.- Parameters:
tableName- the name of an underlying datasource table to be turned into a pretty REST collection name- Returns:
- a camelCased and pluralized version of tableName
- See Also:
-
beautifyName
Try to make an attractive camelCase valid javascript variable name.Lots of sql db designers use things like SNAKE_CASE_COLUMN_NAMES that look terrible as json property names.
- Parameters:
name- the to beautify- Returns:
- a camelCased version of
name - See Also:
-
makeRelationshipName
Attempts to construct a sensible json property name for a 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".
- Parameters:
collection- the collection the relationship name being created will belong torelationship- the relationship- Returns:
- the json property name representing this Relationship.
-
castDbOutput
Casts value as Property.type- Parameters:
property- the property the value is assigned tovalue- the value pulled from the DB- Returns:
valuecast toProperty.type- See Also:
-
castJsonInput
Casts value as Property.type.- Parameters:
property- the property the value is assigned tovalue- the value to cast to the datatype of property- Returns:
valuecast toProperty.type- See Also:
-
castJsonInput
Casts value to as type.- Parameters:
type- the type to cast tovalue- the value to cast- Returns:
valuecast totype- See Also:
-
mapToJsonNames
-
mapToColumnNames
-
getProperty
-
getCollectionByTableName
-
removeCollection
-
getCollections
- Returns:
- a shallow copy of
collections
-
withIncludeTables
Utility that parses a comma and pipe separated list of table name to collection name mappings.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.
- Parameters:
includeTables- underlying data source tables that should be included as REST collections- Returns:
- this
- See Also:
-
withIncludeTable
Whitelists tableName as an underlying table that should be reflectively bootstrapped and exposed as collectionName.- Parameters:
tableName- the table to build a Collection forcollectionName- the name of the target collection that can be different from tableName.- Returns:
- this
-
withCollections
- Parameters:
collections- to include (add not replace)- Returns:
- this
-
withCollection
-
filterOutJsonProperty
Checks if "collectionName.columnName" or just "columnName" is specifically included or excluded viaincludeColumnsexcludeColumnsor 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.
- Parameters:
collection- the collection in questionname- the name of the property to optionally filter- Returns:
- true if the property should be excluded
-
withIncludeColumns
-
withExcludeColumns
-
getName
-
withName
The name of the Db is used primarily for autowiring "name.property" bean props from- Parameters:
name- the name to set- Returns:
- this
-
isType
-
getType
-
withType
-
isBootstrap
public boolean isBootstrap() -
withBootstrap
-
getEndpointPath
-
withEndpointPath
-
isDryRun
public boolean isDryRun() -
withDryRun
-