Package io.inversion

Class Db<T extends Db>

java.lang.Object
io.inversion.Db<T>

public abstract class Db<T extends Db> extends Object
An adapter to an underlying data source.

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:

  1. reflectively generate Collections to represent their underlying tables (or buckets, folders, containers etc.) columns, indexes, and relationships, during #doStartup(Api).
  2. implement REST CRUD support by implementing select(Collection, Map), upsert(Collection, List), delete(Collection, List).

Actions such as DbGetAction then:

  1. translate a REST collection/resource request into columnName based json and RQL,
  2. execute the requested CRUD method on the Collection's underlying Db
  3. then translate the results back from the Db columnName based data into the approperiate jsonName version for external consumption.
  • Field Details

    • reservedParams

      protected static final Set<String> 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

      protected final 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.
    • includeTables

      protected final Map<String,String> includeTables
      A tableName to collectionName map that can be used by whitelist backend tables that should be included in reflicitive Collection creation.
    • includeColumns

      protected final Set<String> includeColumns
      OPTIONAL column names that should be included in RQL queries, upserts and patches.
      See Also:
    • excludeColumns

      protected final Set<String> excludeColumns
      OPTIONAL column names that should be excluded from RQL queries, upserts and patches.
      See Also:
    • bootstrap

      protected boolean bootstrap
      Indicates 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

      protected String name
      The name of his Db used for "name.property" style autowiring.
    • type

      protected String 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

      protected Path 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 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.
  • Constructor Details

    • Db

      public Db()
    • Db

      public Db(String name)
  • Method Details

    • startup

      public final T startup(Api api)
      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

      protected void doStartup(Api api)
      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, calls configDb() once globally and configApi(Api) once for each Api passed in.

      Parameters:
      api - the api to start
      See Also:
    • shutdown

      public T shutdown()
      Shutsdown all running Apis.

      This is primarily a method used for testing.

      Returns:
      this
    • doShutdown

      protected void doShutdown()
    • shutdown

      public T shutdown(Api api)
    • doShutdown

      protected void doShutdown(Api api)
      Made to be overridden by subclasses or anonymous inner classes to do specific cleanup
      Parameters:
      api - the api shutting down
    • isRunning

      public boolean isRunning(Api api)
    • select

      public final Results select(Collection collection, Map<String,String> params) throws ApiException
      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 queried
      params - 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

      public abstract Results doSelect(Collection collection, List<Term> queryTerms) throws ApiException
      Finds all records that match the supplied RQL query terms.
      Parameters:
      collection - the collection to query
      queryTerms - 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

      public String getHref(Object hrefOrNode)
    • getKey

      public Map<String,Object> getKey(Collection collection, Object node)
    • mapTo

      public Map<String,Object> mapTo(Map<String,Object> srcRow, Index srcCols, Index destCols)
    • doUpsert

      public abstract List<String> doUpsert(Collection collection, List<Map<String,Object>> records) throws ApiException
      Upserts 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 modified
      records - 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 ApiException
      Should 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 patch
      records - the key/value pairs to update on existing records
      Returns:
      the keys of all resources modified
      Throws:
      ApiException
    • doPatch

      public void doPatch(Collection collection, List<Map<String,Object>> rows) throws ApiException
      Throws:
      ApiException
    • delete

      public abstract void delete(Collection collection, List<Map<String,Object>> indexValues) throws ApiException
      Deletes rows identified by the unique index values from the underlying data source.

      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.

      Parameters:
      collection - the collection being modified
      indexValues - the identifiers for the records to delete
      Throws:
      ApiException
    • configApi

      protected void configApi(Api api)
      Adds all non excluded Collections to the Api via Api.withCollection
      Parameters:
      api - the Api to configure.
    • configDb

      protected void configDb() throws ApiException
      Subclasses should reflectively create Collections and Properties, Indexes, and Relationships here.

      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.

      Throws:
      ApiException - when configuration fails
    • buildCollections

      protected void buildCollections()
      Creates a collection for every table name in 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.

    • 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

      protected String beautifyCollectionName(String tableName)
      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

      protected String beautifyName(String name)
      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

      protected String makeRelationshipName(Collection collection, Relationship relationship)
      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 to
      relationship - the relationship
      Returns:
      the json property name representing this Relationship.
    • castDbOutput

      public Object castDbOutput(Property property, Object value)
      Casts value as Property.type
      Parameters:
      property - the property the value is assigned to
      value - the value pulled from the DB
      Returns:
      value cast to Property.type
      See Also:
    • castJsonInput

      public Object castJsonInput(Property property, Object value)
      Casts value as Property.type.
      Parameters:
      property - the property the value is assigned to
      value - the value to cast to the datatype of property
      Returns:
      value cast to Property.type
      See Also:
    • castJsonInput

      public Object castJsonInput(String type, Object value)
      Casts value to as type.
      Parameters:
      type - the type to cast to
      value - the value to cast
      Returns:
      value cast to type
      See Also:
    • mapToJsonNames

      protected void mapToJsonNames(Collection collection, Term term)
    • mapToColumnNames

      protected Set<Term> mapToColumnNames(Collection collection, Term term)
    • getProperty

      protected Property getProperty(String tableName, String columnName)
    • getCollectionByTableName

      public Collection getCollectionByTableName(String tableName)
    • removeCollection

      public void removeCollection(Collection table)
    • getCollections

      public List<Collection> getCollections()
      Returns:
      a shallow copy of collections
    • withIncludeTables

      public T withIncludeTables(String includeTables)
      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

      public T withIncludeTable(String tableName, String collectionName)
      Whitelists tableName as an underlying table that should be reflectively bootstrapped and exposed as collectionName.
      Parameters:
      tableName - the table to build a Collection for
      collectionName - the name of the target collection that can be different from tableName.
      Returns:
      this
    • withCollections

      public T withCollections(Collection... collections)
      Parameters:
      collections - to include (add not replace)
      Returns:
      this
    • withCollection

      public T withCollection(Collection collection)
    • filterOutJsonProperty

      public boolean filterOutJsonProperty(Collection collection, String name)
      Checks if "collectionName.columnName" or just "columnName" is specifically included or excluded via 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.

      Parameters:
      collection - the collection in question
      name - the name of the property to optionally filter
      Returns:
      true if the property should be excluded
    • withIncludeColumns

      public T withIncludeColumns(String... columnNames)
    • withExcludeColumns

      public T withExcludeColumns(String... columnNames)
    • getName

      public String getName()
    • withName

      public T withName(String name)
      The name of the Db is used primarily for autowiring "name.property" bean props from
      Parameters:
      name - the name to set
      Returns:
      this
    • isType

      public boolean isType(String... types)
    • getType

      public String getType()
    • withType

      public T withType(String type)
    • isBootstrap

      public boolean isBootstrap()
    • withBootstrap

      public T withBootstrap(boolean bootstrap)
    • getEndpointPath

      public Path getEndpointPath()
    • withEndpointPath

      public T withEndpointPath(Path endpointPath)
    • isDryRun

      public boolean isDryRun()
    • withDryRun

      public T withDryRun(boolean dryRun)