Package io.inversion

Class Db<T extends Db>

  • All Implemented Interfaces:
    java.lang.Comparable<T>

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

    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:

    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 Summary

      Fields 
      Modifier and Type Field 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.
    • Constructor Summary

      Constructors 
      Constructor Description
      Db()  
      Db​(java.lang.String name)  
    • Field Detail

      • reservedParams

        protected static final java.util.Set<java.lang.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 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.
      • includeTables

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

        protected final java.util.Set<java.lang.String> includeColumns
        OPTIONAL column names that should be included in RQL queries, upserts and patches.
        See Also:
        filterOutJsonProperty(Collection, String)
      • excludeColumns

        protected final java.util.Set<java.lang.String> excludeColumns
        OPTIONAL column names that should be excluded from RQL queries, upserts and patches.
        See Also:
        filterOutJsonProperty(Collection, String)
      • 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.

      • type

        protected java.lang.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.

      • 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 Detail

      • Db

        public Db()
      • Db

        public Db​(java.lang.String name)
    • Method Detail

      • excludeTable

        protected boolean excludeTable​(java.lang.String tableName)
      • castJsonInput

        public static java.lang.Object castJsonInput​(java.lang.String type,
                                                     java.lang.Object value)
      • 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(Api)
      • 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:
        configDb(), configApi(Api)
      • 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,
                                    java.util.Map<java.lang.String,​java.lang.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 Results doSelect​(Collection collection,
                                java.util.List<io.inversion.rql.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 java.util.List<java.lang.String> upsert​(Collection collection,
                                                             java.util.List<java.util.Map<java.lang.String,​java.lang.Object>> records)
                                                      throws ApiException
        Throws:
        ApiException
      • doUpsert

        public java.util.List<java.lang.String> doUpsert​(Collection collection,
                                                         java.util.List<java.util.Map<java.lang.String,​java.lang.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 java.util.List<java.lang.String> patch​(Collection collection,
                                                      java.util.List<java.util.Map<java.lang.String,​java.lang.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 java.util.List<java.lang.String> doPatch​(Collection collection,
                                                        java.util.List<java.util.Map<java.lang.String,​java.lang.Object>> rows)
                                                 throws ApiException
        Throws:
        ApiException
      • delete

        public final void delete​(Collection collection,
                                 java.util.List<java.util.Map<java.lang.String,​java.lang.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
      • doDelete

        public void doDelete​(Collection collection,
                             java.util.List<java.util.Map<java.lang.String,​java.lang.Object>> indexValues)
                      throws ApiException
        Throws:
        ApiException
      • configApi

        protected void configApi​(Api api)
        Does some final configuration 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 java.lang.String beautifyCollectionName​(java.lang.String tableName)
        Attempts to camelCase the table name to make it an attractive REST collection name. If includeTables contains tableName as a key, the value from includeTables is returned as is.
        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(String)
      • beautifyName

        protected java.lang.String beautifyName​(java.lang.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:
        JSON property name
      • makeRelationshipName

        protected java.lang.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 java.lang.Object castDbOutput​(Property property,
                                             java.lang.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
      • castJsonInput

        public java.lang.Object castJsonInput​(Property property,
                                              java.lang.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(String, Object)
      • mapToJsonNames

        protected void mapToJsonNames​(Collection collection,
                                      io.inversion.rql.Term term)
      • mapToColumnNames

        protected java.util.Set<io.inversion.rql.Term> mapToColumnNames​(Collection collection,
                                                                        io.inversion.rql.Term term)
      • mapToColumnNames

        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)
      • getProperty

        protected Property getProperty​(java.lang.String tableName,
                                       java.lang.String columnName)
      • getCollection

        public Collection getCollection​(java.lang.String collectionOrTableName)
      • getCollectionByTableName

        public Collection getCollectionByTableName​(java.lang.String tableName)
      • removeCollection

        public void removeCollection​(Collection table)
      • getCollections

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

        public T withIncludeTables​(java.lang.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:
        TODO: this should lazy init off a string config property
      • withIncludeTable

        public 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.
        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,
                                             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.

        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​(java.lang.String... columnNames)
      • withExcludeColumns

        public T withExcludeColumns​(java.lang.String... columnNames)
      • isType

        public boolean isType​(java.lang.String... types)
      • getType

        public java.lang.String getType()
      • withType

        public T withType​(java.lang.String type)
      • isBootstrap

        public boolean isBootstrap()
      • withBootstrap

        public T withBootstrap​(boolean bootstrap)
      • isDryRun

        public boolean isDryRun()
      • withDryRun

        public T withDryRun​(boolean dryRun)