diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index fce1626e..8d4ac2ba 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -27,19 +27,51 @@ env:
EMAIL: jack20220723@gmail.com
jobs:
- yml-md-style-and-link-checks:
- uses: QubitPi/hashistack/.github/workflows/yml-md-style-and-link-checks.yml@master
+ cancel-previous:
+ name: Cancel Previous Runs In Order to Allocate Action Resources Immediately for Current Run
+ if: github.ref != 'refs/heads/master'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cancel previous
+ uses: styfle/cancel-workflow-action@0.10.1
+ with:
+ access_token: ${{ github.token }}
+
+ yaml-lint:
+ name: YAML Style Check
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actionshub/yamllint@main
+
+ markdown-lint:
+ name: Markdown Style Check
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actionshub/markdownlint@main
+
+ linkChecker:
+ name: Link Check
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Link Checker
+ uses: lycheeverse/lychee-action@v1.9.0
+ with:
+ fail: true
tests:
name: Unit & Integration Tests
- needs: yml-md-style-and-link-checks
+ needs: [yaml-lint, markdown-lint, linkChecker]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - name: Test environment setup
- uses: ./.github/actions/ci-setup
- - name: Set up Docker for Integration Tests
- uses: docker-practice/actions-setup-docker@master
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: ${{ env.JDK_VERSION }}
+ distribution: ${{ env.JDK_DISTRIBUTION }}
- name: Run unit & integration tests
run: mvn -B clean verify
@@ -52,8 +84,11 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18
- - name: Test environment setup
- uses: ./.github/actions/ci-setup
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: ${{ env.JDK_VERSION }}
+ distribution: ${{ env.JDK_DISTRIBUTION }}
- name: Install dependencies
working-directory: docs
run: yarn
@@ -82,8 +117,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- - name: Test environment setup
- uses: ./.github/actions/ci-setup
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: ${{ env.JDK_VERSION }}
+ distribution: ${{ env.JDK_DISTRIBUTION }}
- name: Build App WAR file so that Docker can pickup during image build
run: mvn clean package
- name: Set up QEMU
diff --git a/.mdlrc b/.mdlrc
new file mode 100644
index 00000000..a52d9c5d
--- /dev/null
+++ b/.mdlrc
@@ -0,0 +1,2 @@
+rules "~MD002", "~MD003", "~MD005", "~MD007", "~MD013", "~MD022", "~MD024", "~MD027", "~MD028", "~MD029", "~MD033", "~MD034", "~MD036", "~MD041", "~MD055", "~MD057"
+style "#{File.dirname(__FILE__)}/markdownlint.rb"
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 00000000..6584a13c
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,23 @@
+---
+extends: default
+rules:
+ line-length:
+ max: 256
+ level: warning
+ document-start: disable
+ braces:
+ forbid: false
+ min-spaces-inside: 0
+ max-spaces-inside: 1
+ min-spaces-inside-empty: -1
+ max-spaces-inside-empty: -1
+ commas:
+ max-spaces-before: -1
+ min-spaces-after: 1
+ max-spaces-after: -1
+ brackets:
+ max-spaces-inside: -1
+ empty-lines:
+ max: 3
+ indentation:
+ spaces: 2
diff --git a/README.md b/README.md
index dc859dcd..3514e0d3 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,6 @@ Jersey Webservice Template
[Java Version Badge][Java Version Badge]
[![GitHub Workflow Status][GitHub Workflow Status]](https://github.com/QubitPi/jersey-webservice-template/actions/workflows/ci-cd.yml)
-![Last Commit](https://img.shields.io/github/last-commit/QubitPi/jersey-webservice-template/master?logo=github&style=for-the-badge)
[![Apache License Badge]][Apache License, Version 2.0]
[Jersey Webservice Template (JWT)][jersey-webservice-template] is a [JSR 370] web service **template** that lets us
@@ -28,12 +27,6 @@ Coming Soon!
Documentation
-------------
-JWT supports 3 kinds of webservice templates:
-
-- [A general scaffolding without any vertical-business logics](https://qubitpi.github.io/jersey-webservice-template/docs/intro)
-- [A JPA webservice template backed by yahoo/elide](https://qubitpi.github.io/jersey-webservice-template/docs/crud/)
-- An async jobstore webservice template similar to yahoo/fili's JobStore design (Developing...)
-
Comprehensive documentation is viewable on our [website][Documentation]
License
diff --git a/docs/docs/crud/_category_.json b/docs/docs/crud/_category_.json
deleted file mode 100644
index 9ba353b3..00000000
--- a/docs/docs/crud/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "CRUD Webservice",
- "position": 6,
- "link": {
- "type": "generated-index",
- "description": "Spinning Up CRUD Jersey Webservice in a Minute"
- }
-}
\ No newline at end of file
diff --git a/docs/docs/crud/configuration.mdx b/docs/docs/crud/configuration.mdx
deleted file mode 100644
index 9e267a03..00000000
--- a/docs/docs/crud/configuration.mdx
+++ /dev/null
@@ -1,124 +0,0 @@
----
-sidebar_position: 2
-title: Configuration
-description: Configurations specific to JPA through yahoo/elide
----
-
-The configurations in this page can be set from several sources in the following order:
-
-1. the [operating system's environment variables]; for instance, an environment variable can be set with
- `export DB_URL="jdbc:mysql://db/elide?serverTimezone=UTC"`
-2. the [Java system properties]; for example, a Java system property can be set using
- `System.setProperty("DB_URL", "jdbc:mysql://db/elide?serverTimezone=UTC")`
-3. a **.properties** file placed under CLASSPATH. This file can be put under `src/main/resources` source directory with
- contents, for example, `DB_URL=jdbc:mysql://db/elide?serverTimezone=UTC`
-
-Core Properties
----------------
-
-:::note
-
-The following configurations can be placed in the properties file called **application.properties**
-
-:::
-
-- __MODEL_PACKAGE_NAME__: The fully qualified package name that contains a set of Elide JPA models
-
-JPA DataStore
--------------
-
-:::note
-
-The following configurations can be placed in the properties file called **jpadatastore.properties**
-
-:::
-
-- **DB_USER**: Persistence DB username (needs have both Read and Write permissions).
-- **DB_PASSWORD**: The persistence DB user password.
-- **DB_URL**: The persistence DB URL, such as "jdbc:mysql://localhost/elide?serverTimezone=UTC".
-- **DB_DRIVER**: The SQL DB driver class name, such as "com.mysql.jdbc.Driver".
-- **DB_DIALECT**: The SQL DB dialect name, such as "org.hibernate.dialect.MySQLDialect".
-- **HIBERNATE_HBM2DDL_AUTO**: What to do with existing JPA database when webservice starts; can be one of the 4 values:
-
- 1. _validate_: validate that the schema matches, make no changes to the schema of the database. _This is the default
- value of **HIBERNATE_HBM2DDL_AUTO**_
- 2. _update_: update the schema to reflect the entities being persisted
- 3. _create_: creates the schema necessary for your entities, destroying any previous data.
- 4. _create-drop_: create the schema as in create above, but also drop the schema at the end of the session. This is
- great in development or for testing.
-
- :::note
-
- This property is exactly the same as [Hibernate `hibernate.hbm2ddl.auto` property].
-
- :::
-
-CI/CD
------
-
-In addition to the ones mentioned in [general CI/CD configs](../configuration#cicd), these
-[GitHub Action Secrets][GitHub Action - How to set up] needs to be setup:
-
-| Secret Name | Definition | How to Get |
-|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
-| APPLICATION_PROPERTIES | The contents of the `src/main/resources/application.properties` mentioned above | See [Core Properties](#core-properties) section above |
-| JPADATASTORE_PROPERTIES | The contents of the `src/main/resources/jpadatastore.properties` mentioned above | See [JPA DataStore](#jpa-datastore) section above |
-| DATA_MODELS_PRIVATE_REPO_TOKEN | The GitHub Fine-grained token with at least "Read access to code and metadata" repository permissions to the Elide data models repo | [Creating a fine-grained personal access token] |
-| DATA_MODELS_PRIVATE_REPO_ORG | The org/user name of the GitHub repo for Elide data models | For [this example][jersey-webservice-template-jpa-data-models], DATA_MODELS_PRIVATE_REPO_ORG is "QubitPi" |
-| DATA_MODELS_PRIVATE_REPO_NAME | The name of the GitHub repo for Elide data models | For [this example][jersey-webservice-template-jpa-data-models], DATA_MODELS_PRIVATE_REPO_NAME is "jersey-webservice-template" |
-
-### CI/CD Chain
-
-Jersey Webservice Templates adopts the best CI/CD strategies by incorporating its sister projects, [jersey-webservice-template-jpa-data-models] and
-[jersey-webservice-template-jpa-data-models-acceptance-tests], into its CI/CD pipeline. Any PR merge into `jpa-elide` branch will trigger the
-[CI/CD of its data model](https://github.com/QubitPi/jersey-webservice-template-jpa-data-models/actions), which then triggers
-[CI/CD of data model's acceptance tests](https://github.com/QubitPi/jersey-webservice-template-jpa-data-models-acceptance-tests/actions).
-
-The triggering of its direct downstream project is done through GitHub Actions. See the **triggering** job in [CI/CD definition file]. Basically, the triggering is proxied to
-[peter-evans/repository-dispatch]:
-
-```yaml
- triggering:
- name: Triggering data model CI/CD
- runs-on: ubuntu-latest
- steps:
- - name: Trigger data model CI/CD
- uses: peter-evans/repository-dispatch@v2
- with:
- token: ${{ secrets.MY_DATA_MODEL_CICD_TRIGGER }}
- repository: my-org/my-data-model-repo
- event-type: my-webservice-repo-changes
-```
-
-For **MY_DATA_MODEL_CICD_TRIGGER** token, it is recommended to use a
-[fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token)
-with the following permissions on the target repository (i.e. _my-data-model-repo_):
-
-- contents: read & write
-- metadata: read only (automatically selected when selecting the contents permission)
-
-In downstream project CI/CD workflow, add the following to the
-[on-clause](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on):
-
-```yaml
-"on":
- repository_dispatch:
- types: [my-webservice-repo-changes]
-```
-
-Note that how `my-webservice-repo-changes` is used to bridge the event dispatcher (Jersey Webservice Template) and event subscriber (data model project).
-
-[CI/CD definition file]: https://github.com/QubitPi/jersey-webservice-template/blob/jpa-elide/.github/workflows/ci-cd.yml
-[Creating a fine-grained personal access token]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token
-
-[GitHub Action - How to set up]: https://docs.github.com/en/actions/security-guides/encrypted-secrets
-
-[Hibernate `hibernate.hbm2ddl.auto` property]: https://stackoverflow.com/questions/18077327/hibernate-hbm2ddl-auto-possible-values-and-what-they-do
-
-[Java system properties]: https://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html
-[jersey-webservice-template-jpa-data-models]: https://github.com/QubitPi/jersey-webservice-template-jpa-data-models
-[jersey-webservice-template-jpa-data-models-acceptance-tests]: https://github.com/QubitPi/jersey-webservice-template-jpa-data-models-acceptance-tests
-
-[operating system's environment variables]: https://docs.oracle.com/javase/tutorial/essential/environment/env.html
-
-[peter-evans/repository-dispatch]: https://github.com/peter-evans/repository-dispatch
diff --git a/docs/docs/crud/elide/_category_.json b/docs/docs/crud/elide/_category_.json
deleted file mode 100644
index 29a12e4e..00000000
--- a/docs/docs/crud/elide/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Elide Library Documentation",
- "position": 3,
- "link": {
- "type": "generated-index",
- "description": "Spinning Up CRUD Jersey Webservice in a Minute"
- }
-}
\ No newline at end of file
diff --git a/docs/docs/crud/elide/analytics.mdx b/docs/docs/crud/elide/analytics.mdx
deleted file mode 100644
index 1ea7d06a..00000000
--- a/docs/docs/crud/elide/analytics.mdx
+++ /dev/null
@@ -1,1810 +0,0 @@
----
-sidebar_position: 6
-title: Analytic Query Support
----
-
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
-
-Overview
---------
-
-Elide includes a semantic modeling layer and analytic query API for OLAP style queries against our database.
-
-A **semantic model** is the view of the data we want our users to understand. It is typically non-relational (for
-simplicity) and consists of concepts like tables, measures, and dimensions. End users refer to these concepts by name
-only (they are not expected to derive formulas or know about the physical storage or serialization of data).
-
-A **virtual semantic layer** maps a semantic model to columns and tables in a physical database. Elide's virtual
-semantic layer accomplishes this mapping through a [Hjson](https://hjson.github.io/) configuration language. Hjson is a
-human friendly adaptation of JSON that allows comments and a relaxed syntax among other features. Elide's virtual
-semantic layer includes the following information:
-
-- The defintions of tables, measures, and dimensions we want to expose to the end user.
-- Metadata like descriptions, categories, and tags that better describe and label the semantic model.
-- For every table, measure, and dimension, a SQL fragment that maps it to the physical data. These fragements are used
- by elide to generate native SQL queries against the target database.
-
-Elide leverages the `AggregationDataStore` store to expose the read-only models defined in the semantic model. Model
-attributes represent either metrics (for aggregating, filtering, and sorting) and dimensions (for grouping, filtering,
-and sorting). Models exposed through the aggregation store are flat and do not contain relationships to other models.
-
-The Aggregation store includes a companion store, the `MetaDataStore`, which exposes metadata about the Aggregation
-store models including their metrics and dimensions. The metadata store models are predefined, read-only, and served
-from server memory.
-
-There are two mechanisms to create models in the Aggregation store's semantic layer:
-
-1. Through [Hjson](https://hjson.github.io/) configuration files that can be maintained without writing code or
- rebuilding the application.
-2. Through JVM language classes annotated with Elide annotations.
-
-The former is preferred for most use cases because of better ergonomics for non-developers. The latter is useful to add
-custom Elide security rules or life cycle hooks.
-
-Querying
---------
-
-Models managed by the `AggregationDataStore` can be queried via JSON-API or GraphQL similar to other Elide models.
-There are a few important distinctions:
-
-1. If one or more metrics are included in the query, every requested dimension will be used to aggregate the selected
- metrics.
-2. If only dimensions (no metrics) are included in the query, Elide will return a distinct list of the requested
- dimension value combinations.
-3. Every elide model includes an ID field. The ID field returned from aggregation store models is not a true identifier.
- It represents the row number from a returned result. Attempts to load the model by its identifier will result in an
- error.
-
-### Analytic Queries
-
-Similar to other Elide models, analytic models can be sorted, filtered, and paginated. A typical analytic query might
-look like:
-
-
-
-
- ```console
- /playerStats?fields[playerStats]=highScore,overallRating,countryIsoCode&sort=highScore
- ```
-
-
-
-
- ```console
- {
- playerStats(sort: "highScore") {
- edges {
- node {
- highScore
- overallRating
- countryIsoCode
- }
- }
- }
- }
- ```
-
-
-
-
-Conceptually, these queries might generate SQL similar to:
-
-```sql
-SELECT MAX(highScore), overallRating, countryIsoCode FROM playerStats GROUP BY overallRating, countryIsoCode ORDER BY MAX(highScore) ASC;
-```
-
-Here are the respective responses:
-
-
-
-
- ```json
- {
- "data": [
- {
- "type": "playerStats",
- "id": "0",
- "attributes": {
- "countryIsoCode": "HKG",
- "highScore": 1000,
- "overallRating": "Good"
- }
- },
- {
- "type": "playerStats",
- "id": "1",
- "attributes": {
- "countryIsoCode": "USA",
- "highScore": 1234,
- "overallRating": "Good"
- }
- },
- {
- "type": "playerStats",
- "id": "2",
- "attributes": {
- "countryIsoCode": "USA",
- "highScore": 2412,
- "overallRating": "Great"
- }
- }
- ]
- }
- ```
-
-
-
-
- ```json
- {
- "data": {
- "playerStats": {
- "edges": [
- {
- "node": {
- "highScore": 1000,
- "overallRating": "Good",
- "countryIsoCode": "HKG"
- }
- },
- {
- "node": {
- "highScore": 1234,
- "overallRating": "Good",
- "countryIsoCode": "USA"
- }
- },
- {
- "node": {
- "highScore": 2412,
- "overallRating": "Great",
- "countryIsoCode": "USA"
- }
- }
- ]
- }
- }
- }
- ```
-
-
-
-
-### Metadata Queries
-
-A full list of available table and column metadata is covered in the [configuration section](#tables). Metadata can be
-queried through the *table* model and its associated relationships.
-
-
-
-
- ```console
- /table/playerStats?fields[table]=name,category,description,requiredFilter,tags,metrics,dimensions,timeDimensions
- ```
-
-
-
-
- ```console
- {
- table(ids: ["playerStats"]) {
- edges {
- node {
- name
- category
- description
- requiredFilter
- tags
- metrics {edges {node {id}}}
- dimensions {edges {node {id}}}
- timeDimensions {edges {node {id}}}
- }
- }
- }
- }
- ```
-
-
-
-
-Here are the respective responses:
-
-
-
-
- ```json
- {
- "data": {
- "type": "table",
- "id": "playerStats",
- "attributes": {
- "category": "Sports Category",
- "description": "Player Statistics",
- "name": "playerStats",
- "requiredFilter": "",
- "tags": [
- "Game",
- "Statistics"
- ]
- },
- "relationships": {
- "dimensions": {
- "data": [
- {
- "type": "dimension",
- "id": "playerStats.playerName"
- },
- {
- "type": "dimension",
- "id": "playerStats.player2Name"
- },
- {
- "type": "dimension",
- "id": "playerStats.playerLevel"
- },
- {
- "type": "dimension",
- "id": "playerStats.overallRating"
- },
- {
- "type": "dimension",
- "id": "playerStats.countryIsInUsa"
- },
- {
- "type": "dimension",
- "id": "playerStats.countryIsoCode"
- },
- {
- "type": "dimension",
- "id": "playerStats.countryUnSeats"
- },
- {
- "type": "dimension",
- "id": "playerStats.countryNickName"
- },
- {
- "type": "dimension",
- "id": "playerStats.subCountryIsoCode"
- }
- ]
- },
- "metrics": {
- "data": [
- {
- "type": "dimension",
- "id": "playerStats.id"
- },
- {
- "type": "metric",
- "id": "playerStats.lowScore"
- },
- {
- "type": "metric",
- "id": "playerStats.highScore"
- },
- {
- "type": "metric",
- "id": "playerStats.highScoreNoAgg"
- }
- ]
- },
- "timeDimensions": {
- "data": [
- {
- "type": "timeDimension",
- "id": "playerStats.updatedDate"
- },
- {
- "type": "timeDimension",
- "id": "playerStats.recordedDate"
- }
- ]
- }
- }
- }
- }
- ```
-
-
-
-
- ```json
- {
- "data": {
- "table": {
- "edges": [
- {
- "node": {
- "name": "playerStats",
- "category": "Sports Category",
- "description": "Player Statistics",
- "requiredFilter": "",
- "tags": [
- "Game",
- "Statistics"
- ],
- "metrics": {
- "edges": [
- {
- "node": {
- "id": "playerStats.id"
- }
- },
- {
- "node": {
- "id": "playerStats.highScoreNoAgg"
- }
- },
- {
- "node": {
- "id": "playerStats.lowScore"
- }
- },
- {
- "node": {
- "id": "playerStats.highScore"
- }
- }
- ]
- },
- "dimensions": {
- "edges": [
- {
- "node": {
- "id": "playerStats.countryUnSeats"
- }
- },
- {
- "node": {
- "id": "playerStats.overallRating"
- }
- },
- {
- "node": {
- "id": "playerStats.countryNickName"
- }
- },
- {
- "node": {
- "id": "playerStats.player2Name"
- }
- },
- {
- "node": {
- "id": "playerStats.countryIsoCode"
- }
- },
- {
- "node": {
- "id": "playerStats.playerName"
- }
- },
- {
- "node": {
- "id": "playerStats.playerLevel"
- }
- },
- {
- "node": {
- "id": "playerStats.countryIsInUsa"
- }
- },
- {
- "node": {
- "id": "playerStats.subCountryIsoCode"
- }
- }
- ]
- },
- "timeDimensions": {
- "edges": [
- {
- "node": {
- "id": "playerStats.recordedDate"
- }
- },
- {
- "node": {
- "id": "playerStats.updatedDate"
- }
- }
- ]
- }
- }
- }
- ]
- }
- }
- }
- ```
-
-
-
-
-Configuration
--------------
-
-### Feature Flags
-
-There are feature flags that enable Hjson configuration, analytic queries, and [Metadata queries](#metadata-queries)
-respectively:
-
-| Name | Description | Default |
-| ---------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| ------- |
-| `elide.aggregation-store.dynamic-config.enabled` | Enable model creation through the Hjson configuration files. | `false` |
-| `elide.aggregation-store.enabled` | Enable support for data analytic queries. | `false` |
-| `elide.aggregation-store.metadata-store.enabled` | Enable the metadata query APIs exposing the metadata about the Aggregation store models including their metrics and dimensions. | `false` |
-
-
-
-
- Configure in `application.yaml`.
-
- ```yaml
- elide:
- aggregation-store:
- enabled: true
- metadata-store:
- enabled: true
- dynamic-config:
- enabled: true
- ```
-
-
-
-
- Override `ElideStandaloneSettings`.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAnalyticSettings getAnalyticProperties() {
- return new ElideStandaloneAnalyticSettings() {
- @Override
- public boolean enableDynamicModelConfig() {
- return true;
- }
- @Override
- public boolean enableAggregationDataStore() {
- return true;
- }
- @Override
- public boolean enableMetaDataStore() {
- return true;
- }
- };
- }
- }
- ```
-
-
-
-
-### File Layout
-
-Analtyic model configuration can either be specified through JVM classes decorated with Elide annotations _or_ Hjson
-configuration files. Hjson configuration files can be sourced either from the local filesystem or the classpath. If
-Hjson configuration is found in the classpath, the filesystem is ignored. All Hjson configuration must conform to the
-following directory structure:
-
-```
-CONFIG_ROOT/
- ├── models/
- | ├── tables/
- | | ├── model1.hjson
- | | ├── model2.hjson
- | ├── namespaces/
- | | ├── namespace1.hjson
- | | ├── namespace2.hjson
- | ├── security.hjson
- | └── variables.hjson
- ├── db/
- | ├── sql/
- | | ├── db1.hjson
- | ├── variables.hjson
-```
-
-1. Analytic model files are stored in `/models/tables`. Multiple models can be grouped together into a single file.
-2. Analytic models can optionally belong to a namespace - a grouping of related models with the same API prefix.
- Namespace configuration is defined in `/models/namespaces`.
-3. Security rules are stored in `/models/security.hjson`.
-4. Model, namespace, and security Hjson files support variable substitution with variables defined in
- `/models/variables.hjson`.
-5. Data source configurations are stored in `/db/sql`. Multiple configurations can be grouped together into a single
- file.
-6. Data source Hjson files support variable substitution with variables defined in `/db/variables.hjson`.
-
-CONFIG_ROOT can be any directory in the filesystem or classpath. The root configuration location can be set as follows:
-
-
-
-
- Configure in `application.yaml`.
-
- ```yaml
- elide:
- aggregation-store:
- dynamic-config:
- path: src/resources/configs
- ```
-
-
-
-
- Override `ElideStandaloneSettings`.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAnalyticSettings getAnalyticProperties() {
- return new ElideStandaloneAnalyticSettings() {
- @Override
- public String getDynamicConfigPath() {
- return File.separator + "configs" + File.separator;
- }
- };
- }
- }
- ```
-
-
-
-
-### Data Source Configuration
-
-The Aggregation Data Store does not leverage JPA, but rather uses JDBC directly. By default, Elide will leverage the
-default JPA configuration for establishing connections through the Aggregation Data Store. However, more complex
-configurations are possible including:
-
-1. Using a different JDBC data source other than what is configured for JPA.
-2. Leveraging multiple JDBC data sources for different Elide models.
-
-For these complex configurations, we must configure Elide using the Aggregation Store's Hjson configuration language.
-The following configuration file illustrates two data sources. Each data source configuration includes:
-
-1. A name that will be referenced in our Analytic models (effectively binding them to a data source).
-2. A JDBC URL
-3. A JDBC driver
-4. A user name
-5. An [Elide SQL Dialect](#dialects). This can either be the name of an Elide supported dialect _or_ it can be the fully
- qualified class name of an implementation of an Elide dialect.
-6. A map of driver specific properties.
-
-```
-{
- dbconfigs:
- [
- {
- name: Presto Data Source
- url: jdbc:presto://localhost:4443/testdb
- driver: com.facebook.presto.jdbc.PrestoDriver
- user: guestdb2
- dialect: PrestoDB
- }
- {
- name: Hive Data Source
- url: jdbc:hive2://localhost:4444/dbName
- driver: org.apache.hive.jdbc.HiveDriver
- user: guestmysql
- dialect: com.paiondata.elide.datastores.aggregation.queryengines.sql.dialects.impl.HiveDialect
- propertyMap:
- {
- sslEnabled : true
- }
- }
- ]
-}
-```
-
-By default, Elide uses HikariCP's DataSource for JDBC connection pool. A custom `DataSourceConfiguration` can be
-configured by the following override:
-
-
-
-
- Create a `@Configuration` class that defines our custom implementation as a `@Bean`.
-
- ```java
- @Configuration
- public class ElideConfiguration {
- @Bean
- public DataSourceConfiguration dataSourceConfiguration() {
- return new DataSourceConfiguration() {
- @Override
- public DataSource getDataSource(DBConfig dbConfig, DBPasswordExtractor dbPasswordExtractor) {
- HikariConfig config = new HikariConfig();
-
- config.setJdbcUrl(dbConfig.getUrl());
- config.setUsername(dbConfig.getUser());
- config.setPassword(dbPasswordExtractor.getDBPassword(dbConfig));
- config.setDriverClassName(dbConfig.getDriver());
- dbConfig.getPropertyMap().forEach((k, v) -> config.addDataSourceProperty(k, v));
-
- return new HikariDataSource(config);
- }
- };
- }
- }
- ```
-
-
-
-
- Override `ElideStandaloneSettings`.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public DataSourceConfiguration getDataSourceConfiguration() {
- return new DataSourceConfiguration() {
- @Override
- public DataSource getDataSource(DBConfig dbConfig, DBPasswordExtractor dbPasswordExtractor) {
- HikariConfig config = new HikariConfig();
-
- config.setJdbcUrl(dbConfig.getUrl());
- config.setUsername(dbConfig.getUser());
- config.setPassword(dbPasswordExtractor.getDBPassword(dbConfig));
- config.setDriverClassName(dbConfig.getDriver());
- dbConfig.getPropertyMap().forEach((k, v) -> config.addDataSourceProperty(k, v));
-
- return new HikariDataSource(config);
- }
- };
- }
- }
- ```
-
-
-
-
-#### Data Source Passwords
-
-Data source passwords are provided out of band by implementing a `DBPasswordExtractor`:
-
-```java
-public interface DBPasswordExtractor {
- String getDBPassword(DBConfig config);
-}
-```
-
-A custom `DBPasswordExtractor` can be configured by the following override:
-
-
-
-
- Create a `@Configuration` class that defines our custom implementation as a `@Bean`.
-
- ```java
- @Configuration
- public class ElideConfiguration {
- @Bean
- public DBPasswordExtractor dbPasswordExtractor() {
- return new DBPasswordExtractor() {
- @Override
- public String getDBPassword(DBConfig config) {
- return StringUtils.EMPTY;
- }
- };
- }
- }
- ```
-
-
-
-
- Override `ElideStandaloneSettings`.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAnalyticSettings getAnalyticProperties() {
- return new ElideStandaloneAnalyticSettings() {
- @Override
- public DBPasswordExtractor getDBPasswordExtractor() {
- return new DBPasswordExtractor() {
- @Override
- public String getDBPassword(DBConfig config) {
- return StringUtils.EMPTY;
- }
- };
- }
- };
- }
- }
- ```
-
-
-
-
-#### Dialects
-
-A dialect must be configured for Elide to correctly generate analytic SQL queries. Elide supports the following dialects
-out of the box:
-
-| Friendly Name | Class |
-| ------------- |-------------------------------------------------------------------------------------------|
-| H2 | com.paiondata.elide.datastores.aggregation.queryengines.sql.dialects.impl.H2Dialect |
-| Hive | com.paiondata.elide.datastores.aggregation.queryengines.sql.dialects.impl.HiveDialect |
-| PrestoDB | com.paiondata.elide.datastores.aggregation.queryengines.sql.dialects.impl.PrestoDBDialect |
-| Postgres | com.paiondata.elide.datastores.aggregation.queryengines.sql.dialects.impl.PostgresDialect |
-| MySQL | com.paiondata.elide.datastores.aggregation.queryengines.sql.dialects.impl.MySQLDialect |
-| Druid | com.paiondata.elide.datastores.aggregation.queryengines.sql.dialects.impl.DruidDialect |
-
-If not leveraging Hjson configuration, a default dialect can be configured for analytic queries:
-
-
-
-
- Configure in `application.yaml`.
-
- ```yaml
- elide:
- aggregation-store:
- default-dialect: H2
- ```
-
-
-
-
- Override `ElideStandaloneSettings`.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAnalyticSettings getAnalyticProperties() {
- return new ElideStandaloneAnalyticSettings() {
- @Override
- public String getDefaultDialect() {
- return "Hive";
- }
- };
- }
- }
- ```
-
-
-
-
-### Model Configuration
-
-#### Concepts
-
-Elide exposes a virtual semantic model of tables and columns that represents a data warehouse. The virtual semantic
-model can be mapped to one or more physical databases, tables, and columns through configuration by a data analyst. The
-analyst maps virtual tables and columns to fragments of native SQL queries that are later assembled into complete SQL
-statements at query time.
-
-Analytic models are called **Tables** in Elide. They are made up of:
-
-1. **Metrics** - Numeric columns that can be aggregated, filtered on, and sorted on.
-2. **Dimensions** - Columns that can be grouped on, filtered on, and sorted on.
-3. **TimeDimension** - A type of **Dimension** that represents time. Time dimensions are tied to grain (a period) and a
- timezone.
-4. **Columns** - The supertype of **Metrics**, **Dimensions**, and **TimeDimensions**. All columns share a set of common
- metadata.
-5. **Joins** - Even though Elide analytic models are flat (there are no relationships to other models), individual model
- columns can be sourced from multiple physical tables. **Joins** provide Elide the information it needs to join other
- database tables at query time to compute a given column.
-6. **Namespace** - Every **Table** maps to one **Namespace** or the *default* **Namespace** if undefined. **Namespaces**
- group related tables together that share a common API prefix.
-
-Other concepts include:
-
-1. **Arguments** - **Tables** and **Columns** can optionally have **Arguments**. They represent parameters that are
- supplied by the client to change how the column or table SQL is generated.
-2. **Table Source** - **Columns** and **Arguments** can optionally include metadata about their distinct legal values. **Table Source** references another **Column** in a different **Table** where the values are stored.
-
-#### Example Configuration
-
-
-
-
- ```hjson
- {
- tables: [{
- name: PlayerStats
- table: playerStats
- dbConnectionName: Presto Data Source
- friendlyName: Player Stats
- description:
- '''
- A long description
- '''
- category: Sports
- cardinality : large
- readAccess : '(user AND member) OR (admin.user AND NOT guest user)'
- filterTemplate : createdOn>={{start}};createdOn<{{end}}
- isFact : true
- tags: ['Game', 'Player']
- joins: [
- {
- name: playerCountry
- to: PlayerCountry
- kind: toOne
- type: left
- definition: '{{playerCountry.$id}} = {{$country_id}}'
- }
- ]
- measures : [
- {
- name : highScore
- type : INTEGER
- definition: 'MAX({{$highScore}})'
- friendlyName: High Score
- }
- ]
- dimensions : [
- {
- name : name
- type : TEXT
- definition : '{{$name}}'
- cardinality : large
- },
- {
- name : countryCode
- type : TEXT
- definition : '{{playerCountry.isoCode}}'
- friendlyName: Country Code
- },
- {
- name : gameType
- type : TEXT
- definition : '{{$game_type}}'
- friendlyName: Game Type
- },
- {
- name : gameOn
- type : TIME
- definition : '{{$game_on}}'
- grains:
- [
- {
- type: MONTH
- sql: PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM'), 'yyyy-MM')
- },
- {
- type: DAY
- sql: PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM-dd'), 'yyyy-MM-dd')
- },
- {
- type: SECOND
- sql: PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM-dd HH:mm:ss'), 'yyyy-MM-dd HH:mm:ss')
- }
- ]
- },
- {
- name : createdOn
- type : TIME
- definition : '{{$created_on}}'
- grains:
- [{
- type : DAY
- sql : '''
- PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM-dd'), 'yyyy-MM-dd')
- '''
- }]
- },
- {
- name : updatedOn
- type : TIME
- definition : '{{$updated_on}}'
- grains:
- [{
- type : MONTH
- sql : '''
- PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM'), 'yyyy-MM')
- '''
- }]
- }
- ]
- }]
- }
- ```
-
-
-
-
- ```java
- @Include
- @VersionQuery(sql = "SELECT COUNT(*) from playerStats")
- @FromTable(name = "playerStats", dbConnectionName = "Presto Data Source")
- @TableMeta(description = "A long description", category = "Sports", tags = {"Game", "Player"}, filterTemplate = "createdOn>={{start}};createdOn<{{end}}", size = CardinalitySize.LARGE, friendlyName = "Player Stats")
- @ReadPermission(expression = "(user AND member) OR (admin.user AND NOT guest user)")
- public class PlayerStats {
-
- public static final String DATE_FORMAT = "PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM-dd'), 'yyyy-MM-dd')";
- public static final String YEAR_MONTH_FORMAT = "PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM'), 'yyyy-MM')";
-
- @Id
- private String id;
-
- @MetricFormula("MAX({{$highScore}})")
- @ColumnMeta(friendlyName = "High Score")
- private long highScore;
-
- @ColumnMeta(size = CardinalitySize.LARGE)
- private String name;
-
- @Join("{{$country_id}} = {{playerCountry.$id}}", type = JoinType.LEFT)
- private Country playerCountry;
-
- @DimensionFormula("{{playerCountry.isoCode}}")
- @ColumnMeta(friendlyName = "Country Code")
- private String countryCode;
-
- @DimensionFormula("{{$game_type}}")
- @ColumnMeta(friendlyName = "Game Type")
- private String gameType;
-
- @Temporal(grains = {
- @TimeGrainDefinition(grain = TimeGrain.DAY, expression = DATE_FORMAT),
- @TimeGrainDefinition(grain = TimeGrain.MONTH, expression = YEAR_MONTH_FORMAT)
- }, timeZone = "UTC")
- @DimensionFormula("{{$game_on}}")
- private Time gameOn;
-
- @Temporal(grains = { @TimeGrainDefinition(grain = TimeGrain.DAY, expression = DATE_FORMAT) }, timeZone = "UTC")
- @DimensionFormula("{{$created_on}}")
- private Time createdOn;
-
- @Temporal(grains = { @TimeGrainDefinition(grain = TimeGrain.MONTH, expression = YEAR_MONTH_FORMAT) }, timeZone = "UTC")
- @DimensionFormula("{{$updated_on}}")
- private Time updatedOn;
- }
- ```
-
-
-
-
-#### Handlebars Templates
-
-There are a number of locations in the model configuration that require a SQL fragment. These include:
-
-- Column definitions
-- Table query definitions
-- Table join expressions
-
-SQL fragments cannot refer to physical database tables or columns directly by name. Elide generates SQL queries at
-runtime, and these queries reference tables and columns by aliases that are also generated. Without the correct alias,
-the generated SQL query will be invalid. Instead, physical table and column names should be substituted with
-[handlebars](https://handlebarsjs.com/guide/) template expressions.
-
-All SQL fragments support handlebars template expressions. The handlebars context includes the following fields we can
-reference in our templated SQL:
-
-1. `{{$columnName}}` - Expands to the correctly aliased, physical database column name for the current Elide model.
-2. `{{columnName}}` - Expands another column in the current Elide model.
-3. `{{joinName.column}}` - Expands to a column in another Elide model joined to the current model through the referenced
- join.
-4. `{{joinName.$column}}` - Expands to the correctly aliased, physical database column name for another Elide model
- joined to the current model through the referenced join.
-5. `{{$$table.args.argumentName}}` - Expands to a table argument passed by the client or extracted from the client query
- filter through a table [filterTemplate](#filter-templates).
-6. `{{$$column.args.argumentName}}` - Expands to a column argument passed by the client or extracted from the client
- query filter through a column [filterTemplate](#filter-templates). $$column always refers to the current column that
- is being expanded.
-7. `{{$$column.expr}}` - Expands to a column's SQL fragment. $$column always refers to the current column that is being
- expanded.
-
-Join names can be linked together to create a path from one model to another model's column through a set of joins. For
-example the handlebar expression: `{{join1.join2.join3.column}}` references a column that requires three separate joins.
-
-The templating engine also supports a custom handlebars helper that can reference another column and provide overridden
-column arguments:
-
-1. `{{sql column='columnName[arg1:value1][arg2:value2]'}}` - Expands to a column in the current Elide model with
- argument values explicitly set.
-2. `{{sql from='joinName' column='columnName'}}` - Identical to `{{joinName.columnName}}`.
-3. `{{sql from='joinName' column='$columnName'}}` - Identical to `{{joinName.$columnName}}`.
-4. `{{sql from='joinName' column='columnName[arg1:value1]'}}` - Identical to `{{joinName.columnName}}` but passing
- 'value1' for the column argument, 'arg1'.
-
-The helper takes two arguments:
-
-1. **column** - The column to expand. Optional column arguments (`[argumentName:argumentValue]`) can be appended after
- the column name.
-2. **from** - An optional argument containing the join name where to source the column from. If not present, the column
- is sourced from the current model.
-
-#### Tables
-
-Tables must source their columns from somewhere. There are three, mutually exclusive options:
-
-1. Tables can source their columns from a physical table by its name.
-2. Tables can source their columns from a SQL subquery.
-3. Tables can extend (override or add columns to) an existing Table. More details can be found [here](#inheritance).
-
-These options are configured via the 'table', 'sql', and 'extend' [properties](#table-properties).
-
-##### Table Properties
-
-Tables include the following simple properties:
-
-| Hjson Property | Explanation | Hjson Value | Annotation/Java Equivalent |
-| --------------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|-----------------------------------------------------------|
-| name | The name of the elide model. It will be exposed through the API with this name. | tableName | `@Include(name="tableName")` |
-| version | If leveraging Elide API versions, the API version associated with this model. | 1.0 | `@ApiVersion(version="1.0")` |
-| friendlyName | The friendly name for this table. Unicode characters are supported. | 'Player Stats' | `@TableMeta(friendlyName="Player Stats")` |
-| description | A description of the table. | 'A description for tableName' | `@TableMeta(description="A description for tableName")` |
-| category | A free-form text category for the table. | 'Some Category' | `@TableMeta(category="Some Category")` |
-| tags | A list of free-form text labels for the table. | ['label1', 'label2'] | `@TableMeta(tags={"label1","label2"})` |
-| cardinality | tiny, small, medium, large, huge - A hint about the number of records in the table. | small | `@TableMeta(size=CardinalitySize.SMALL)` |
-| dbConnectionName | The name of the physical data source where this table can be queried. This name must match a data source configuration name. | MysqlDB | `@FromTable(dbConnectionName="MysqlDB")` |
-| schema | The database schema where the physical data resides | schemaName | `@FromTable(name=schemaName.tableName)` |
-| table | Exactly one of _table_, _sql_, and _extend_ must be provided. Provides the name of the physical base table where data will be sourced from. | tableName | `@FromTable(name=tableName)` |
-| sql | Exactly one of _table_, _sql_, and _extend_ must be provided. Provides a SQL subquery where the data will be sourced from. | 'SELECT foo, bar FROM blah;' | `@FromSubquery(sql="SELECT foo, bar FROM blah;")` |
-| extend | Exactly one of _table_, _sql_, and _extend_ must be provided. This model extends or inherits from another analytic model. | tableName | class Foo extends Bar |
-| readAccess | An elide permission rule that governs read access to the table. | 'member and admin.user' | `@ReadPermission(expression="member and admin.user")` |
-| filterTemplate | An RSQL filter expression template that either must directly match the client provided filter or be conjoined with logical 'and' to the client provided filter. | countryIsoCode==\{\{code\}\} | @TableMeta(filterTemplate="countryIsoCode==\{\{code\}\}") |
-| hidden | The table is not exposed through the API. | true | `@Exclude` |
-| isFact | Is the table a fact table. Models annotated using FromTable or FromSubquery or TableMeta or configured through Hjson default to true unless marked otherwise. Yavin will use this flag to determine which tables can be used to build reports. | true | `@TableMeta(isFact=false)` |
-| namespace | The namepsace this table belongs to. If none is provided, the default namespace is presumed. | SalesNamespace | `@Include(name="namespace")` on the Java package. |
-| hints | A list of optimizer hints to enable for this particular table. This is an [experimental feature](#query-optimization). | ['AggregateBeforeJoin'] | @TableMeta(hints="AggregateBeforeJoin") |
-
-Tables also include:
-
-- A list of [columns](#columns) including measures, dimensions, and time dimensions.
-- A list of [joins](#joins).
-- A list of [arguments](#arguments).
-
-
-
-
- ```hjson
- {
- tables:
- [
- {
- namespace: SalesNamespace
- name: orderDetails
- friendlyName: Order Details
- description: Sales orders broken out by line item.
- category: revenue
- tags: [Sales, Revenue]
- cardinality: large
- isFact: true
- filterTemplate: 'recordedDate>={{start}};recordedDate<{{end}}'
-
- #Instead of table, could also specify either 'sql' or 'extend'.
- table: order_details
- schema: revenue
- dbConnectionName: SalesDBConnection
- hints: [AggregateBeforeJoin]
-
- readAccess: guest user
-
- arguments: []
- joins: []
- measures: []
- dimensions: []
- }
- ]
- }
- ```
-
-
-
-
-
- ```java
- @Include(name = "SalesNamespace")
- package example;
-
- import com.paiondata.elide.annotation.Include;
- ```
-
-
-
-
-
- ```java
- @Include(name = "orderDetails") //Tells Elide to expose this model in the API.
- @VersionQuery(sql = "SELECT COUNT(*) from playerStats") //Used to detect when the cache is stale.
- @FromTable( //Could also be @FromSubquery
- name = "revenue.order_details",
- dbConnectionName = "SalesDBConnection"
- )
- @TableMeta(
- friendlyName = "Order Details",
- description = "Sales orders broken out by line item.",
- category = "revenue",
- tags = {"Sales", "Revenue"},
- size = CardinalitySize.LARGE,
- isFact = true,
- filterTemplate = "recordedDate>={{start}};recordedDate<{{end}}",
- hints = {"AggregateBeforeJoin"},
- )
- @ReadPermission(expression = "guest user")
- public class OrderDetails extends ParameterizedModel { //ParameterizedModel is a required base class if any columns take arguments.
- //...
- }
- ```
-
-
-
-
-#### Columns
-
-Columns are either measures, dimensions, or time dimensions. They all share a number of
-[common properties](#column-properties). The most important properties are:
-
-1. The name of the column.
-2. The data type of the column.
-3. The definition of the column.
-
-Column definitions are [templated, native SQL fragments](#handlebars-templates). Columns definitions can include
-references to other column definitions or physical column names that are expanded at query time. Column expressions can
-be defined in Hjson or Java:
-
-
-
-
- ```hjson
- {
- measures : [
- {
- name : highScore
- type : INTEGER
- definition: 'MAX({{$highScore}})'
- }
- ]
- dimensions : [
- {
- name : name
- type : TEXT
- definition : '{{$name}}'
- },
- {
- name : countryCode
- type : TEXT
- definition : '{{playerCountry.isoCode}}'
- }
- ]
- }
- ```
-
-
-
-
-
- ```java
- // A Dimension
- @DimensionFormula("CASE WHEN {{$name}} = 'United States' THEN true ELSE false END")
- private boolean inUsa;
-
- // A metric
- @MetricFormula("{{wins}} / {{totalGames}} * 100")
- private float winRatio;
- ```
-
-
-
-
-##### Column Properties
-
-Columns include the following properties:
-
-| Hjson Property | Explanation | Example Hjson Value | Annotation/Java Equivalent |
-| --------------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|----------------------------------------------------------------------------------------|
-| name | The name of the column. It will be exposed through the API with this name. | columnName | String columnName; |
-| friendlyName | The friendly name for this column to be displayed in the UI. | 'Country Code' | `@ColumnMeta(friendlyName = "Country Code")` |
-| description | A description of the column. | 'A description for columnA' | `@ColumnMeta(description="A description for columnA")` |
-| category | A free-form text category for the column. | 'Some Category' | `@ColumnMeta(category="Some Category")` |
-| tags | A list of free-form text labels for the column. | ['label1', 'label2'] | `@ColumnMeta(tags={"label1","label2"})` |
-| cardinality | tiny, small, medium, large, huge - A hint about the dimension's cardinality. | small | `@ColumnMeta(size=CardinalitySize.SMALL)` |
-| readAccess | An elide permission rule that governs read access to the column. | 'admin.user' | `@ReadPermission(expression="admin.user")` |
-| definition | A SQL fragment that describes how to generate the column. | MAX(\{\{sessions\}\}) | @DimensionFormula("CASE WHEN \{\{name\}\} = 'United States' THEN true ELSE false END") |
-| type | The data type of the column. One of 'INTEGER', 'DECIMAL', 'MONEY', 'TEXT', 'COORDINATE', 'BOOLEAN' or a fully qualified java class name (dimensions only). | 'BOOLEAN' | String columnName; |
-| hidden | The column is not exposed through the API. | true | `@Exclude` |
-
-Non-time dimensions include the following, mutually exclusive properties that describe the set of discrete, legal values
-(for type-ahead search or other usecases) :
-
-| Hjson Property | Explanation | Example Hjson Value | Annotation/Java Equivalent |
-| --------------------- |----------------------------------------------------------------------------------|---------------------------------------------------|------------------------------------------------------------|
-| values | An optional enumerated list of dimension values for small cardinality dimensions | ['Africa', 'Asia', 'North America'] | `@ColumnMeta(values = {"Africa", "Asia", "North America")` |
-| tableSource | The semantic table and column names where to find the values | See the section on [Table Source](#table-source). | See the section on [Table Source](#table-source). |
-
-##### Time Dimensions And Time Grains
-
-Time dimensions represent time and include one or more time grains. The time grain determines how time is represented as
-text in query filters and query results. Supported time grains include:
-
-| Grain | Text Format |
-| ------------ | --------------------- |
-| SECOND | "yyyy-MM-dd HH:mm:ss" |
-| MINUTE | "yyyy-MM-dd HH:mm" |
-| HOUR | "yyyy-MM-dd HH" |
-| DAY | "yyyy-MM-dd" |
-| WEEK | "yyyy-MM-dd" |
-| ISOWEEK | "yyyy-MM-dd" |
-| MONTH | "yyyy-MM" |
-| QUARTER | "yyyy-MM" |
-| YEAR | "yyyy" |
-
-When defining a time dimension, a native SQL expression may be provided with the grain to convert the underlying column
-(represented as `{{$$column.expr}}`) to its expanded SQL definition:
-
-
-
-
- ```hjson
- {
- name : createdOn
- type : TIME
- definition : "FORMATDATETIME({{$createdOn}}, 'yyyy-MM')"
- grains:
- [{
- type : MONTH
- sql : '''
- PARSEDATETIME({{$$column.expr}}, 'yyyy-MM')
- '''
- }]
- }
- ```
-
-
-
-
-
- ```java
- public static final String DATE_FORMAT = "PARSEDATETIME({{$$column.expr}}, 'yyyy-MM')";
-
- @Temporal(grains = {@TimeGrainDefinition(grain = TimeGrain.MONTH, expression = DATE_FORMAT)}, timeZone = "UTC")
- @DimensionFormula("FORMATDATETIME({{$createdOn}}, 'yyyy-MM')")
- private Date createdOn;
- ```
-
-
-
-
-Elide would expand the above example to this SQL fragment:
-`PARSEDATETIME(FORMATDATETIME(createdOn, 'yyyy-MM'), 'yyyy-MM')`.
-
-Time grain definitions are optional and default to type 'DAY' with a native SQL expression of `{{$\$column.expr}}`.
-
-#### Joins
-
-Table joins allow column expressions to reference fields from other tables. At query time, if a column requires a join,
-the join will be added to the generated SQL query. Each table configuration can include zero or more join definitions:
-
-
-
-
- ```hjson
- joins: [
- {
- name: playerCountry
- to: country
- kind: toOne
- type: left
- definition: '{{$country_id}} = {{playerCountry.$id}}' # 'playerCounty' here is the join name.
- },
- {
- name: playerTeam
- to: team
- kind: toMany
- type: full
- definition: '{{$team_id}} = {{playerTeam.$id}}' # 'playerTeam' here is the joinName.
- }
- ]
- ```
-
-
-
-
-
- ```java
- private Country country;
- private Team team;
-
- //'country' here is the the join/field name.
- @Join("{{$country_id}} = {{country.$id}}", type = JoinType.LEFT)
- public Country getCountry() {
- return country;
- }
-
- //'team' here is the the join/field name.
- @Join("{{$team_id}} = {{team.$id}}", type = JoinType.FULL)
- public Team getTeam() {
- return team;
- }
- ```
-
-
-
-
-##### Join Properties
-
-Each join definition includes the following properties:
-
-| Hjson Property | Explanation |
-| --------------------- |---------------------------------------------------------------------------------------------------|
-| name | A unique name for the join. The name can be referenced in column definitions. |
-| namespace | The namepsace the join table belongs to. If none is provided, the default namespace is presumed. |
-| to | The name of the Elide model being joined against. This can be a semantic model or a CRUD model. |
-| kind | 'toMany' or 'toOne' (Default: toOne) |
-| type | 'left', 'inner', 'full' or 'cross' (Default: left) |
-| definition | A templated SQL join expression. See below. |
-
-##### Join Definition
-
-Join definitions are [templated SQL expressions](#handlebars-templates) that represent the *ON* clause of a SQL
-statement:
-
-```
-definition: "{{$orderId}} = {{delivery.$orderId}} AND {{delivery.$delivered_on }} > '1970-01-01'"
-```
-
-#### Arguments
-
-Columns and tables can both be parameterized with arguments. Arguments include the following properties:
-
-| Hjson Property | Explanation |
-| --------------------- |------------------------------------------------------------------|
-| name | The name of the argument |
-| description | The argument description |
-| type | The [primitive type](#column-and-argument-types) of the argument |
-| values | An optional list of allowed values |
-| default | An optional default value if none is supplied by the client |
-
-In addition, arguments can also optionally reference a [Table Source](#table-source). The properties `values` and
-`tableSource` are mutually exclusive.
-
-#### Column And Argument Types
-
-Column and argument values are mapped to primitive types which are used for validation, serialization, deserialization,
-and formatting.
-
-The following primitive types are supported:
-
-1. **Time** - Maps to [Elide supported time grains](#time-dimensions-and-time-grains).
-2. **Integer** - Integer number.
-3. **Decimal** - Decimal number.
-4. **Money** - A decimal number that represents money.
-5. **Text** - A text string.
-6. **Coordinate** - A text representation of latitude, longitude or both.
-7. **Boolean** - true or false.
-8. **Id** - Represents the ID of the model. For analytic models, this is the row number and not an actual primary key.
-
-Input values (filter values, column arguments, or table arguments) are validated by:
-
-1. Type coercion to the underlying Java data type.
-2. Regular expression matching using the
- [following rules](https://github.com/paion-data/elide/blob/master/elide-datastore/elide-datastore-aggregation/src/main/java/com/paiondata/elide/datastores/aggregation/metadata/enums/ValueType.java).
-
-#### Table Source
-
-Table sources contain additional metadata about where distinct legal values of a column or argument can be found. This
-metadata is intended to aid presentation layers with search suggestions.
-
-| Hjson Property | Explanation |
-| --------------------- |------------------------------------------------------------------------------------------------------------------------------|
-| table | The table where the distinct values can be located. |
-| namespace | The namespace that qualifies the table. If not provided, the default namespace is presumed. |
-| column | The column in the table where the distinct values can be located |
-| suggestionColumns | Zero or more additional columns that should be searched in conjunction with the primary column to locate a particular value. |
-
-
-
-
- ```hjson
- dimensions : [
- {
- name : countryNickname
- type : TEXT
- definition : '{{country.nickName}}'
- tableSource : {
- table: country
- column: nickName
- suggestionColumns: [name, description]
- }
- }
- ]
- ```
-
-
-
-
- ```java
- @DimensionFormula("{{country.nickName}}")
- @ColumnMeta(
- tableSource = @TableSource(table = "country", column = "nickName", suggestionColumn = {"name", "description"})
- )
- private String countryNickName;
- ```
-
-
-
-
-#### Namespaces
-
-Namespaces organize a set of related tables together so that they can share:
-
-- Package level metadata like name, friendly name, and description.
-- Default read permission rules for every table in the namespace.
-- A common API prefix that is prepended to each model name in the namespace (`NamespaceName_ModelName`).
-
-While, namespaces are optional, all tables belong to one and only one namespace. If no namespace is defined, the table
-will belong to the 'default' namespace. The default namespace does not have an API prefix.
-
-
-
-
- ```hjson
- {
- namespaces:
- [
- {
- name: SalesNamespace
- description: Namespace for Sales Schema Tables
- friendlyName: Sales
- readAccess: Admin or SalesTeam
- }
- ]
- }
- ```
-
-
-
-
- ```java
- @Include(
- name = "SalesNamespace",
- description = "Namespaces for Sales Schema Tables",
- friendlyName = "Sales"
- )
- @ReadPermission(expression = "Admin or SalesTeam")
- package example;
-
- import com.paiondata.elide.annotation.Include;
- import com.paiondata.elide.annotation.ReadPermission;
- ```
-
-
-
-
-#### Inheritance
-
-Tables can extend another existing Table. The following actions can be performed:
-
-* New columns can be added.
-* Existing columns can be modified.
-* [Table properties](#table-properties) can be modified.
-
-The Table properties listed below can be inherited without re-declaration. Any [Table property](#table-properties) not
-listed below, has to be re-declared.
-
-* `dbConnectionName`
-* `schema`
-* `table`
-* `sql`
-
-Unlike [Table properties](#table-properties), [Column properties](#column-properties) are not inherited. When overriding
-a Column in an extended Table, the column properties have to be redefined.
-
-##### Hjson inheritance v.s. Java inheritance
-
-Hjson inheritance and Java inheritance differ in one key way. Hjson inheritance allows the type of a measure or
-dimension to be changed in the subclassed model. Changing the type of an inherited measure or dimension in Java might
-generate a compilation error.
-
-##### Example Extend Configuration
-
-The sample below uses the [Example Configuration](#example-configuration) as its parent model. Let's assume we are a
-club that exposes the Player Stats from the intra-squad practice games and the tournament games to coaches using the
-PlayerStats model. We want to expose the data from the same persistent store to the general public with the following
-differences:
-
-- Exclude the intra-squad games from `highScore` calculation.
-- Modify the Grain of `game_on` column from `DAY` to `YEAR`.
-- Accessible by Admins and Guest users.
-
-To avoid the compilation error highlighted [above](#hjson-inheritance-vs-Java-inheritance), we will have to write the
-new JVM class with all the columns and properties instead of inheriting unchanged ones from the Parent model. With the
-Hjson `extend`, it will be a few lines of simple changes to inherit from the Parent model without duplication as
-highlighted in the example below.
-
-
-
-
- ```hjson
- {
- tables: [{
- name: TournamentPlayerStats
- extend: PlayerStats
- readAccess : 'admin.user OR guest user'
- measures : [
- {
- name : highScore
- type : INTEGER
- definition: MAX(CASE WHEN {{gameType}} = 'tournament' THEN {{highScore}}) ELSE NULL END)
- }
- ],
- dimensions : [
- {
- name : gameOn
- type : TIME
- definition : '{{$game_on}}'
- # Change Type from MONTH, DAY & SECOND to YEAR & MONTH
- grains:
- [
- {
- type: YEAR
- sql: PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy'), 'yyyy')
- },
- {
- type: MONTH
- sql: PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM'), 'yyyy-MM')
- }
- ]
- }
- ]
- }]
- }
- ```
-
-
-
-
- ```java
- @Include
- @VersionQuery(sql = "SELECT COUNT(*) from playerStats")
- @ReadPermission(expression = "admin.user OR guest user")
- @FromTable(name = "playerStats", dbConnectionName = "Presto Data Source")
- public class TournamentPlayerStats {
-
- public static final String DATE_FORMAT = "PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM-dd'), 'yyyy-MM-dd')";
- public static final String YEAR_MONTH_FORMAT = "PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy-MM'), 'yyyy-MM')";
- public static final String YEAR_FORMAT = "PARSEDATETIME(FORMATDATETIME({{$$column.expr}}, 'yyyy'), 'yyyy')";
-
- @Id
- private String id;
-
- // Change formula to filter on Tournament Games
- @MetricFormula("MAX(CASE WHEN {{gameType}} = 'tournament' THEN {{highScore}}) ELSE NULL END)")
- @ColumnMeta(friendlyName = "High Score")
- private long highScore;
-
- @ColumnMeta(size = CardinalitySize.LARGE)
- private String name;
-
- @Join("{{$country_id}} = {{playerCountry.$id}}", type = JoinType.LEFT)
- private Country playerCountry;
-
- @DimensionFormula("{{playerCountry.isoCode}}")
- @ColumnMeta(friendlyName = "Country Code")
- private String countryCode;
-
- @DimensionFormula("{{$game_type}}")
- @ColumnMeta(friendlyName = "Game Type")
- private String gameType;
-
- @Temporal(grains = { @TimeGrainDefinition(grain = TimeGrain.MONTH, expression = YEAR_MONTH_FORMAT) }, timeZone = "UTC")
- @DimensionFormula("{{$updated_on}}")
- private Time updatedOn;
-
- @Temporal(grains = { @TimeGrainDefinition(grain = TimeGrain.DAY, expression = DATE_FORMAT) }, timeZone = "UTC")
- @DimensionFormula("{{$created_on}}")
- private Time createdOn;
-
- // Change types of gameOn from Day & Month to Day, Month & Year
- @Temporal(grains = {
- @TimeGrainDefinition(grain = TimeGrain.DAY, expression = DATE_FORMAT),
- @TimeGrainDefinition(grain = TimeGrain.MONTH, expression = YEAR_MONTH_FORMAT)
- @TimeGrainDefinition(grain = TimeGrain.YEAR, expression = YEAR_FORMAT)
- }, timeZone = "UTC")
- @DimensionFormula("{{$game_on}}")
- private Time gameOn;
- }
- ```
-
-
-
-
-We can use Java's inheritance, if the goal does not involve changing the type of columns. Hjson `extend` will still require a few lines of simple changes.
-
-
-
-
- ```hjson
- {
- tables: [{
- name: TournamentPlayerStats
- extend: PlayerStats
- readAccess : 'admin.user OR guest user'
- measures : [
- {
- name : highScore
- type : INTEGER
- definition: MAX(CASE WHEN {{gameType}} = 'tournament' THEN {{highScore}}) ELSE NULL END)
- }
- ]
- }]
- }
- ```
-
-
-
-
- ```java
- @Include
- @ReadPermission(expression = "admin.user OR guest user")
- public class TournamentPlayerStats extends PlayerStats {
-
- // Change formula to filter on Tournament Games
- @MetricFormula("MAX(CASE WHEN {{gameType}} = 'tournament' THEN {{highScore}}) ELSE NULL END)")
- private long highScore;
- }
- ```
-
-
-
-
-### Security Configuration
-
-The semantics of security are described [here](#security).
-
-HJSON has limited support for security definitions. Currently, only role based access controls
-([user checks](security#user-chekcs)) can be defined in HJSON. For more elaborate rules, the Elide security checks must
-be written in code.
-
-A list of available user roles can be defined in HJSON in the `security.hjson` file:
-
-```
-{
- roles : [
- admin.user
- guest user
- member
- user
- ]
-}
-```
-
-Each role defined generates an Elide [user check](security#user-chekcs) that extends
-**RoleMemberCheck** defined in
-[Role](https://github.com/paion-data/elide/blob/master/elide-core/src/main/java/com/paiondata/elide/core/security/checks/prefab/Role.java).
-
-These roles can then be referenced in security rules applied to entire tables or individual columns in their respective
-Hjson configuration:
-
-`readAccess = 'member OR guest user'`
-
-The `readAccess` table and column attribute can also reference Elide checks that are compiled with our application to
-implement row level security or other more complex security rules.
-
-### Variable Substitution
-
-To avoid repeated configuration blocks, all Hjson files (table, security, and data source) support variable
-substitution. Variables are defined in the variables.hjson file:
-
-```
-{
- foo: [1, 2, 3]
- bar: blah
- hour: hour_replace
- measure_type: MAX
- name: PlayerStats
- table: player_stats
-}
-```
-
-The file format is a simple mapping from the variable name to a JSON structure. At server startup, Elide will replace
-any variable name surrounded by `<%` and `%>` tags with the corresponding JSON structure.
-
-### Caching
-
-The Aggregation data store supports a configurable caching strategy to cache query results. More details can be found in
-the [performance section](performance#aggregationdatastore-cache).
-
-#### Bypassing Cache
-
-Elide JAX-RS endpoints (elide-standalone) and Spring controllers (Spring) support a Bypass Cache header ('bypasscache')
-that can be set to `true` for caching to be disabled on a per query basis. If no bypasscache header is specified by the
-client or a value other than `true` is used, caching is enabled by default.
-
-Security
---------
-
-Elide analytic models differ from CRUD models in some important ways. In a client query on a CRUD model backed by JPA,
-all model fields are hydrated (in some cases with lazy proxies) regardless of what fields the client requests.
-In an analytic query, only the model fields requested are hydrated. Checks which can execute in memory on the Elide
-server ([Operation](security#operation-checks) & [Filter Expression](security#filter-expression-checks) checks) may
-examine fields that are not hydrated and result in errors for analytic queries. To avoid this scenario, the Aggregation
-Store implements its own permission executor with different restrictions and semantics.
-
-The aggregation store enforces the following model permission restrictions:
-
-- [Operation checks](security#operation-checks) are forbidden.
-- [Filter Expression checks](security#filter-expression-checks) may only decorate the model but not its fields.
-- [User checks](security#user-chekcs) are allowed anywhere.
-
-Unlike CRUD models, model 'read' permissions are not interpreted as field permission defaults. Model and field
-permissions are interpreted independently.
-
-Elide performs the following authorization steps when reading records:
-
-1. Determine if the database query can be avoided (by only evaluating checks on the user principal).
-2. Filter records in the database (by evaluating only filter expression checks).
-3. Filter records in memory (by evaluating all checks on each record returned from the database).
-4. Verify the client has permission to filter on the fields in the client's filter expression (by evaluating field level
- permissions).
-5. Prune fields from the response that the client cannot see (by evaluating field level permissions).
-
-The aggregation store will prune rows returned in the response (steps 1-3) by evaluating the following expression:
-
-```
-(entityRule AND (field1Rule OR field2Rule ... OR fieldNRule)
-```
-
-Step 4 and 5 simply evaluates the user checks on each individual field.
-
-Experimental Features
----------------------
-
-### Configuration Validation
-
-All Hjson configuration files are validated by a JSON schema. The schemas for each file type can be found here:
-
-1. [Table Config](https://github.com/paion-data/elide/blob/master/elide-model-config/src/main/resources/elideTableSchema.json)
-1. [Data Source Config](https://github.com/paion-data/elide/blob/master/elide-model-config/src/main/resources/elideDBConfigSchema.json)
-1. [Security Config](https://github.com/paion-data/elide/blob/master/elide-model-config/src/main/resources/elideSecuritySchema.json)
-1. [Variable File](https://github.com/paion-data/elide/blob/master/elide-model-config/src/main/resources/elideVariableSchema.json)
-
-Hjson configuration files can be validated against schemas using a command-line utility following these steps:
-
-1. Build your Elide project to generate a Fat JAR. Make sure to include a Fat JAR build configuration in your POM file.
-
- `mvn clean install`
-
-2. Using the generated JAR for validation:
-
- `java -cp elide-*-example.jar com.paiondata.elide.modelconfig.validator.DynamicConfigValidator --help`
- `java -cp elide-*-example.jar com.paiondata.elide.modelconfig.validator.DynamicConfigValidator --configDir `
-
-3. The config directory needs to adhere to this [file layout](#file-layout).
-
-### Query Optimization
-
-Some queries run faster if aggregation is performed prior to joins (for dense joins). Others my run faster if
-aggregation is performed after joins (for sparse joins). By default, Elide generates queries that first aggregatoin and
-then join. Elide includes an experimental optimizer that will rewrite the queries to aggregate first and then join.
-This can be enabled at the table level by providing the hint, 'AggregateBeforeJoin' in the table configuration.
-
-### Filter Templates
-
-A filter template is a RSQL filter expression that must match (in whole or in part) the client's query (or the client
-query will be rejected). Filter templates can be added to either table or column definitions. At the table level, the
-filter template must match every query against the table. At the column level, the template is only required to match
-if the client query explicitly requests the particular column.
-
-#### Variable extraction
-
-A filter template can optionally contain a template variable on the right hand side of any predicate. These variables
-are assigned to the values provided in the client query filter and added to the table arguments (for table filter
-templates) or the column arguments (for column filter templates). For example, the following filterTemplate would add
-the variables 'start' and 'end' to the table arguments:
-
-```
-{
- tables:
- [
- {
- name: orderDetails
- filterTemplate : deliveryTime>={{start}};deliveryTime<{{end}}
-
- ...
-```
-
-#### Matching
-
-A filter templates matches a client query if one of the two conditions holds:
-
-- The filter template exactly matches the client filter.
-- The filter template exactly matches part of the client filter that is conjoined with logical 'and' to the remainder of
- the client filter.
-
-For example, the client RSQL filter `lowScore>100;(highScore>=100;highScore<999)` matches the template
-`highScore>={{low}};highScore<{{high}}`.
diff --git a/docs/docs/crud/elide/audit.md b/docs/docs/crud/elide/audit.md
deleted file mode 100644
index fe3f4e58..00000000
--- a/docs/docs/crud/elide/audit.md
+++ /dev/null
@@ -1,429 +0,0 @@
----
-sidebar_position: 9
-title: Logging & Audit
-description: Configuring logging & audit
----
-
-Logging
--------
-
-Elide emits a number of useful log messages that can aid in debugging. This section will cover common configurations to
-capture Elide's most useful messages. It will also cover common logging tasks outside Elide including HTTP
-request/response logging, request tracing, and database query logging. All examples use Spring Boot configured with
-logback. However, most of the concepts apply regardless of the logging framework used.
-
-### Elide JPQL/HQL Logging
-
-When using the JPA or Hibernate datastores, Elide generates
-[HQL/JPQL](https://docs.oracle.com/html/E13946_04/ejb3_langref.html) queries that are sent to the
-[ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) layer. These queries are similar to SQL but they use the
-model names instead of physical table names.
-
-To enable logging to see these queries, set the following property (based on the data store) to DEBUG:
-
-```xml
-
-
-```
-
-```xml
-
-
-```
-
-```xml
-
-
-```
-
-This will enable logs similar to:
-
-```text
-Query Hash: 1839872383 HQL Query: SELECT example_models_ArtifactGroup FROM example.models.ArtifactGroup AS example_models_ArtifactGroup
-```
-
-### Query Latency Logging
-
-To get information about how long Elide JPQL or analytic queries are taking, we can enable timings:
-
-```xml
-
-
-```
-
-This will enable logs similar to:
-
-```text
-Query Hash: 1839872383 Time spent: 14
-```
-
-Not the query hash matches the JPQL log statement. The time spent is given in milliseconds.
-
-### Elide Error Response Logging
-
-To get extra information why a particular error was returned to a client, enable the following properties to DEBUG:
-
-```xml
-
-
-
-```
-
-This is particularly helpful to understand what permissions in a complex permission rule have passed, failed, or were
-not evaluated. For example, the following indicates that _'User is Admin'_ permission rule failed:
-
-```text
-ForbiddenAccessException: Message=CreatePermission: CREATE PERMISSION WAS INVOKED ON PersistentResource{type=post, id=2}
-WITH CHANGES ChangeSpec { resource=PersistentResource{type=post, id=2}, field=abusiveContent, original=false,
-modified=true} FOR EXPRESSION [FIELD((User is Admin FAILED))] Mode=Optional[ALL_CHECKS] Expression=[Optional
-[CREATE PERMISSION WAS INVOKED ON PersistentResource{type=post, id=2} WITH CHANGES ChangeSpec {
-resource=PersistentResource{type=post, id=2}, field=abusiveContent, original=false, modified=true} FOR EXPRESSION
-[FIELD((User is Admin FAILED))]]]
-```
-
-### Elide Error Response Entity Bodies
-
-It is also possible to return these verbose messages as an entity body in HTTP requests that failed due to
-Authorization:
-
-```HTTP
-HTTP/1.1 403 Forbidden
-Date: Sat, 14 Dec 2019 03:33:08 GMT
-Content-Type: application/vnd.api+json
-Content-Length: 291
-Server: Jetty(9.4.24.v20191120)
-
-{
- "errors": [
- "CreatePermission: CREATE PERMISSION WAS INVOKED ON PersistentResource{type=post, id=2} WITH CHANGES ChangeSpec
- { resource=PersistentResource{type=post, id=2}, field=abusiveContent, original=false, modified=true} FOR
- EXPRESSION [FIELD((User is Admin FAILED))]"
- ]
-}
-```
-
-By default these descriptions are disabled. They can be turned on in Elide Settings
-
-#### Elide Standalone
-
-If using [Elide standalone][elide-standalone], override the following function in `ElideStandaloneSettings` and enable
-verbose errors:
-
-```java
-@Override
-public boolean verboseErrors() {
- return true;
-}
-```
-
-#### Elide Spring Boot
-
-If using [Elide spring boot][elide-spring], set the following setting in application.yml:
-
-```yaml
-elide:
- verbose-errors: true
-```
-
-### Hibernate SQL Logging
-
-We can configure Hibernate to display the SQL commands it runs including the parameters it binds to prepared statements:
-
-```xml
-
-
-
-```
-
-This will produce logs like:
-
-```text
-select products0_.group_name as group_na4_1_0_, products0_.name as name1_1_0_, products0_.name as name1_1_1_,
-products0_.commonName as commonNa2_1_1_, products0_.description as descript3_1_1_, products0_.group_name as
-group_na4_1_1_ from ArtifactProduct products0_ where products0_.group_name=?
-binding parameter [1] as [VARCHAR] - [com.paiondata.elide]
-```
-
-Be sure to configure Hibernate to show SQL in the JDBC configuration as well:
-
-#### Spring Boot Application YAML
-
-```yaml
-spring:
- jpa:
- show-sql: true
-```
-
-#### Elide Standalone Settings
-
-```java
-@Override
-public Properties getDatabaseProperties() {
- Properties options = new Properties();
- ...
-
- options.put("hibernate.show_sql", "true");
- return options;
-}
-```
-
-### HTTP Request & Response Logging
-
-Sometimes it is useful to log the actual HTTP request and response bodies (be careful in production if the entity bodies
-contain sensitive data). This example requires spring boot and logback-access-spring-boot-starter:
-
-```xml
-
- net.rakugakibox.spring.boot
- logback-access-spring-boot-starter
- ${logback-acccess-version}
-
-```
-
-The actual logging of the requests and responses is performed by Logback's
-[TeeFilter](http://logback.qos.ch/recipes/captureHttp.html). To add the servlet filter, we must provide the
-`FilterRegistrationBean` as follows:
-
-```java
-@Configuration
-public class FilterConfiguration {
-
- @Bean
- public FilterRegistrationBean requestResponseFilter() {
- final FilterRegistrationBean filterRegBean = new FilterRegistrationBean<>();
- TeeFilter filter = new TeeFilter();
- filterRegBean.setFilter(filter);
- filterRegBean.addUrlPatterns("/*");
- filterRegBean.setName("Elide Request Response Filter");
- filterRegBean.setAsyncSupported(Boolean.TRUE);
- return filterRegBean;
- }
-}
-```
-
-Finally, configure logback access by creating a `logback-access-spring.xml` file in our classpath. This one writes logs
-to a rotating file (the location is defined in the application yaml `logging.path`):
-
-```xml
-
-
-
-
-
-
- ${logDir}/access.log
-
- ${logDir}/archived/access_%d{yyyy-MM-dd}.log
- 30
- 100MB
-
-
- %t{yyyy-MM-dd:HH:mm:ss Z} %remoteIP %user %requestURL %statusCode %bytesSent %elapsedTime %header{X-B3-TraceId} %requestContent %responseContent
-
-
-
-
-
-```
-
-The pattern extracts the following fields from the HTTP request & response:
-
-| Field Name | Explanation |
-|-----------------------------|-------------------------------------------|
-| `%t{yyyy-MM-dd:HH:mm:ss Z}` | The date and time of the log |
-| remoteIP | The remote IP address |
-| requestURL | The request URL |
-| statusCode | The HTTP status code of the response |
-| bytesSent | Response content length |
-| elapsedTime | Time in milliseconds to serve the request |
-| `%header{X-B3-TraceId}` | Tracing Header used to track requests |
-| requestContent | The request entity body |
-| responseContent | The response entity body |
-
-The 'X-B3-TraceId' header can be used to match request tracing in the server logs. An example access log would look
-like:
-
-```console
-2019-12-14:15:48:53 -0600 0:0:0:0:0:0:0:1 - GET /api/v1/group HTTP/1.1 200 496 385 0000000000000005 {"data":[
-{"type":"group","id":"com.example.repository","attributes":{"commonName":"Example Repository","description":"The code
-for this project"},"relationships":{"products":{"data":[]}}},{"type":"group","id":"com.paiondata.elide","attributes":
-{"commonName":"Elide","description":"The magical library powering this project"},"relationships":{"products":{"data":[
-{"type":"product","id":"elide-core"},{"type":"product","id":"elide-standalone"},{"type":"product",
-"id":"elide-datastore-hibernate5"}]}}}]}
-```
-
-### Request Tracing & Server Logs
-
-This example uses [Spring Cloud Sleuth](https://cloud.spring.io/spring-cloud-sleuth/reference/html/) without
-[Zipkin](https://zipkin.io/) integration:
-
-```xml
-
- org.springframework.cloud
- spring-cloud-starter-sleuth
- ${spring-cloud-sleuth-version}
-
-```
-
-Cloud Sleuth will use [logback MDC logging](http://logback.qos.ch/manual/mdc.html) to pass (if provided in headers) or
-set a number of unique identifiers that can be added to log statements to trace requests. These headers
-('X-B3-TraceId' and 'X-B3-SpanId') can also be logged in the access log to get the complete picture of a request.
-
-The following logback-spring.xml file can be added to your classpath. It does the following:
-
-1. Logs to the console and a rotating file.
-2. Turns on Elide, JPQL, and Hibernate logging.
-3. Logs the time, thread identifier, request trace identifier (X-B3-TraceId), log level, log class, and finally the log
- message.
-
-```xml
-
-
-
-
-
-
-
-
- %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) [${springAppName}, %X{X-B3-TraceId:-}] %highlight(%-5level) %logger{36}.%M - %msg%n
-
-
-
-
- ${logDir}/server.log
-
- %d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] [${springAppName}, %X{X-B3-TraceId:-}] %-5level %logger{36}.%M - %msg%n
-
-
- ${logDir}/archived/server_%d{yyyy-MM-dd}.log
- 30
- 100MB
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-Result log files will look like:
-
-```console
-14-12-2019 15:48:53.329 [qtp1863374262-22] [Elide, d426047505ceef4e] DEBUG c.y.e.d.j.p.EntityManagerWrapper.logQuery -
-HQL Query: SELECT example_models_ArtifactGroup FROM example.models.ArtifactGroup AS example_models_ArtifactGroup
-```
-
-### Analytic Query Logging
-
-Analytic queries are logged by the `AggregationDataStore` directly. To log the generated SQL and other information,
-enable the following property to DEBUG:
-
-```xml
-
-
-```
-
-Result log files will look like:
-
-```text
-30-10-2020 16:23:12.301 [task-1] [Elide, 1f7de407f8554500] DEBUG c.y.e.d.a.core.Slf4jQueryLogger.log - QUERY ACCEPTED:
-{"id":"5c9a1f64-09fa-451c-87f7-c0bcb2b76135","user":"Unknown","apiVersion":"","path":"/downloads","headers":{}}
-
-30-10-2020 16:23:12.327 [task-1] [Elide, 1f7de407f8554500] DEBUG c.y.e.d.a.core.Slf4jQueryLogger.log - QUERY RUNNING:
-{"id":"5c9a1f64-09fa-451c-87f7-c0bcb2b76135","queries":["SELECT SUM(dynamicconfig_models_Downloads.downloads) AS
-downloads,dynamicconfig_models_Downloads_artifactProduct.name AS product,dynamicconfig_models_Downloads_artifactGroup.
-name AS groupy,dynamicconfig_models_Downloads.date AS date FROM downloads AS dynamicconfig_models_Downloads LEFT JOIN
-ArtifactProduct AS dynamicconfig_models_Downloads_artifactProduct ON dynamicconfig_models_Downloads.product_id =
-dynamicconfig_models_Downloads_artifactProduct.name LEFT JOIN ArtifactGroup AS
-dynamicconfig_models_Downloads_artifactGroup ON dynamicconfig_models_Downloads.group_id =
-dynamicconfig_models_Downloads_artifactGroup.name GROUP BY dynamicconfig_models_Downloads_artifactProduct.name,
-dynamicconfig_models_Downloads_artifactGroup.name, dynamicconfig_models_Downloads.date LIMIT 500 OFFSET 0"],
-"isCached":false}
-
-30-10-2020 16:23:12.338 [task-1] [Elide, 1f7de407f8554500] DEBUG c.y.e.d.a.core.Slf4jQueryLogger.log - QUERY COMPLETE:
-{"id":"5c9a1f64-09fa-451c-87f7-c0bcb2b76135","status":200,"error":null}
-```
-
-Audit
------
-
-Elide provides an Audit mechanism that assigns semantic meaning to CRUD operations for the purposes of logging and
-audit. For example, we may want to log when users change their password or when an account is locked. Both actions are
-mutations on a user entity that update different fields. Audit can assign these actions to parameterized, human readable
-logging statements that can be logged to a file, written to a database, etc.
-
-### Core Concepts
-
-A model's **lineage** is the path taken through the entity relationship graph to reach it. A model and every prior model
-in its lineage are fully accessible to parameterize audit logging in Elide.
-
-### Annotations
-
-Elide audits operations on classes and class fields marked with the `Audit` annotation.
-
-The `Audit` annotation takes several arguments:
-
-1. The CRUD action performed (CREATE, DELETE, or UPDATE).
-2. An operation code which uniquely identifies the semantic meaning of the action.
-3. The statement to be logged. This is a template string that allows '{}' variable substitution.
-4. An ordered list of [Unified Expression Language](https://uel.java.net/) expressions that are used to substitute '{}'
- in the log statement. Elide binds the model that is being audited and every model in its lineage to variables that
- are accessible to the UEL expressions. The variable names map to model's type (typically the class name).
-
-### Example
-
-Let's say we have a simple _user_ entity with a _password_ field. We want to audit whenever the password is changed. The
-user is accessed via the URL path '/company/53/user/21'. We could annotate this action as follows:
-
-```java
-@Entity
-@Include
-public class User {
-
- @Audit(action = Audit.Action.UPDATE,
- operation = 572,
- logStatement = "User {0} from company {1} changed password.",
- logExpressions = {"${user.userid}", "${company.name}"})
- private String password;
- private String userid;
-}
-```
-
-Elide binds the `User` object to the variable name _user_ and the `Company` object to the variable name _company_. The
-`Company` object is bound because it belongs to the `User` object's lineage.
-
-### Customizing Logging
-
-Customizing audit functionality in Elide requires two steps:
-
-1. Define audit annotations on JPA entity classes and fields.
-2. Provide a Logger implementation to customize the handling of audit triggers. The default logger simply logs to
- [slf4j](http://www.slf4j.org/).
-
-### Logger Implementation
-
-A customized logger extends the following abstract class:
-
-```java
-public abstract class AuditLogger {
-
- public void log(LogMessage message);
- public abstract void commit() throws IOException;
-}
-```
-
-[elide-spring]: https://github.com/paion-data/elide/tree/master/elide-spring/elide-spring-boot-autoconfigure
-[elide-standalone]: https://github.com/paion-data/elide/tree/master/elide-standalone
diff --git a/docs/docs/crud/elide/clientapis/_category_.json b/docs/docs/crud/elide/clientapis/_category_.json
deleted file mode 100644
index 959d3332..00000000
--- a/docs/docs/crud/elide/clientapis/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Client APIs",
- "position": 8,
- "link": {
- "type": "generated-index",
- "description": "JSON API and GraphQL Client APIs"
- }
-}
diff --git a/docs/docs/crud/elide/clientapis/asyncapi.mdx b/docs/docs/crud/elide/clientapis/asyncapi.mdx
deleted file mode 100644
index 6eb75527..00000000
--- a/docs/docs/crud/elide/clientapis/asyncapi.mdx
+++ /dev/null
@@ -1,800 +0,0 @@
----
-sidebar_position: 6
-title: Async API
-description: Async support for both JSON API and GraphQL
----
-
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
-
-Overview
---------
-
-Elide APIs are designed for synchronous request and response behavior. The time allowed to service a synchronous
-response can be limited by proxy servers and gateways. Analytic queries can often take longer than these limits and
-result in a server timeout. Elide's asynchronous API decouples the submission of a request and the delivery of the
-response into separate client calls. Some of the features available are:
-
-- Queries are run in a background thread that posts the results into a persistence store.
-- Results can be retrieved as an embedded response or URL for downloading later.
-- Supported downloading formats - CSV and JSON.
-- Queries can be configured to execute synchronously before switching to asynchronous mode if not finished by a client
- provided threshold.
-- Queries that are interrupted due to an application crash/reboot are automatically flagged as TIMEDOUT.
-- Persisted queries and results are deleted after a configurable threshold.
-
-Design
-------
-
-### API
-
-The Async API supports two different query abstractions built using standard Elide models (AsyncQuery and TableExport):
-
-- Any read-only API request in JSON-API or GraphQL can be submitted by creating an AsyncQuery object. After creation,
- the client can poll the AsyncQuery object asynchronously for status updates. Once complete, the AsyncQuery object
- stores the query result as an embedded attribute.
-- For large response bodies, Elide supports a streaming abstraction called TableExport. It works similarly to the
- AsyncQuery model but with three important differences:
-
- 1. TableExport can only be leveraged when:
-
- - The query is a simple read.
- - The model being queried has no relationships.
- - Only one model is being queried in the request.
-
- 2. Upon successful completion, the TableExport model includes a separate URL attribute that references the query
- results for streaming downloads.
- 3. The results of the GraphQL or JSON-API query are converted into a simple, flat JSON or CSV format.
-
-Example API requests and responses can be found [here](asyncapi#running).
-
-### Threading
-
-Each Elide instance runs a scheduler that is responsible for executing these requests in background threads. New async
-requests are initially marked in the QUEUED state. The requests are picked for execution as the threads become
-available. Upon completion, the background thread posts the query status and results to a persistent store. The size of
-the thread pool can be configured as mentioned [here](asyncapi#additional-configuration).
-
-### Async After
-
-The Async requests can be configured to execute synchronously before switching to asynchronous mode. The requests not
-finished synchronously by the client provided threshold are handed off to a separate thread for posting the results once
-complete. The default value for `async-after` is 10 seconds. Setting `async-after` to 0 will execute the request in
-asynchronous mode upon submission.
-
-### Background Cleaner
-
-Each Elide instance will also run a scheduler for maintenance and cleanup.
-
-- It cleans up requests and results stored in the persistent store.
-- It scans and flags the queries that failed due to an application crash/reboot automatically as TIMEDOUT.
-- It is responsible for the graceful cancellation of async requests.
-
- - The user can update the status of the async requests to CANCELLED. The cleaner polls the AsyncQuery and TableExport
- models to find any new requests that were CANCELLED and tries to terminate the transaction associated with that
- execution and change the status to CANCEL_COMPLETE.
- - Long-running transactions that exceed the run time threshold are terminated. Any background thread executing the
- original request is interrupted.
-
-- The retention, polling interval, and max run time thresholds can be configured during application startup.
-- This scheduler can be disabled by setting `cleanup.enabled` to false as mentioned
- [here](asyncapi#enable-the-async-api).
-
-### TableExport Results Download
-
-Elide has built-in support for streaming the results of a TableExport request through the export endpoint. Upon
-successful completion, the TableExport model includes a separate URL attribute where results can be downloaded from.
-
-Enabling the end-point, timeouts, path, download attachment extensions, etc. can be configured during application
-startup as mentioned [here](asyncapi#additional-configuration).
-
-### Supported Query Types
-
-Below are the supported values for query type in asynchronous calls:
-
-* GRAPHQL_V1_0
-* JSONAPI_V1_0
-
-### Supported Result Types
-
-Elide can transform the results into a pre-selected format while persisting them via the
-[ResultStorageEngine](asyncapi#overriding-the-resultstorageengine). Below are the supported formats for Table Export
-results:
-
-- JSON
-- CSV
-
-### Query Status
-
-Below are the different states of an asynchronous request:
-
-| Status | Description |
-| --------------- | ------------------------------------------------------------------------------------- |
-| QUEUED | Request is submitted and waiting to be picked up for execution. |
-| PROCESSING | Request has been picked up for execution. |
-| COMPLETE | Request has completed. |
-| CANCELLED | The client has requested to cancel a running request. |
-| TIMEDOUT | Request did not finish within the configured maximum run time. |
-| FAILURE | Request not completed due to one or more failures encountered by the scheduler. |
-| CANCEL_COMPLETE | Request has been canceled by the background cleaner. |
-
-Malformed or invalid queries provided in the Async request will finish with COMPLETE status and the actual error message
-will be available in the `result` property of AsyncQuery and TableExport models.
-
-Security
---------
-
-The Async API models (AsyncQuery and TableExport) have a simple permission model: Only the principal who submitted a
-query and principals which belong to an administrative role are allowed to retrieve its status or results. Principals
-can be assigned roles when constructing the Elide [user](security#user) object.
-
-Enable the Async API
---------------------
-
-By default the async API is disabled. The elide models (AsyncQuery and TableExport) needed to support the Async API are
-JPA [models][demo-schema] that are mapped to a specific database schema. This schema must be created in our target
-database. Feel free to modify the query/result column sizes if needed.
-
-| Name | Description | Default |
-| ----------------------------------|----------------------------------------------------------------------------------------------------------------------------| -----------|
-| `elide.async.enabled` | Enable the Async API feature. | `false` |
-| `elide.async.cleanup.enabled` | Enable cleaning up of Async API requests history, update the status of interrupted/timedout requests, and cancel requests. | `false` |
-
-
-
-
- Configure in `application.yaml`.
-
- If we rely on Spring to autodiscover the entities which are placed in the same package/sub-package as the application
- class with `@SpringBootApplication` annotation, we will have to add the `@EntityScan` annotation to that application
- class for those entities to be discovered after async is enabled.
-
- ```yaml
- elide:
- async:
- enabled: true
- cleanup:
- enabled: true
- ```
-
-
-
-
- Override `ElideStandaloneSettings`.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAsyncSettings getAsyncProperties() {
- ElideStandaloneAsyncSettings asyncProperties = new ElideStandaloneAsyncSettings() {
- @Override
- public boolean enabled() {
- return true;
- }
-
- @Override
- public boolean enableCleanup() {
- return true;
- }
- }
- return asyncProperties;
- }
- }
- ```
-
-
-
-
-### Additional Configuration
-
-These additional configuration settings control timeouts, cleanup, export end-point, resultStorageEngine and the sizes of thread pools.
-
-| Name | Description | Default Value
-|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|---------------
-| `elide.async.enabled` | Whether or not the async feature is enabled. | `false`
-| `elide.async.thread-pool-size` | Default thread pool size. | `5`
-| `elide.async.max-async-after` | Default maximum permissible time to wait synchronously for the query to complete before switching to asynchronous mode. | `10s`
-| `elide.async.cleanup.enabled` | Whether or not the cleanup is enabled. | `false`
-| `elide.async.cleanup.query-max-run-time` | Maximum query run time. | `3600s`
-| `elide.async.cleanup.query-retention-duration` | Retention period of async query and results before being cleaned up. | `7d`
-| `elide.async.cleanup.query-cancellation-interval` | Polling interval to identify async queries that should be canceled. | `300s`
-| `elide.async.export.enabled` | Whether or not the controller is enabled. | `false`
-| `elide.async.export.path` | The URL path prefix for the controller. | `/export`
-| `elide.async.export.append-file-extension` | Enable Adding Extension to table export attachments. | `false`
-| `elide.async.export.storage-destination` | Storage engine destination. | `/tmp`
-| `elide.async.export.format.csv.write-header` | Generates the header in a CSV formatted export. | `true`
-
-These additional configuration settings are only applicable for Elide’s Standalone module. When using Spring, please
-configure the TaskExecutor used by Spring MVC for executing and managing the asynchronous requests.
-
-| Name | Description | Default Value
-|-------------------------------|-------------------------------------------------------------------------------|-------------------------------------------------
-| `exportAsyncResponseTimeout` | Default timeout for TableExport's result download end-point. | `30s` |
-| `exportAsyncResponseExecutor` | Executor for executing TableExport's result download request asynchronously. | A java.util.concurrent.ExecutorService instance |
-
-
-
-
- Configure in `application.yaml`.
-
- ```yaml
- elide:
- async:
- thread-pool-size: 10
- max-async-after: 30s
- cleanup:
- enabled: true
- query-max-run-time: 120s
- query-retention-duration: 10d
- query-cancellation-check-interval: 600s
- export:
- enabled: true
- path: /export
- storage-destination: /tmp
- format:
- csv:
- write-header: true
- ```
-
-
-
-
- Override `ElideStandaloneSettings`.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAsyncSettings getAsyncProperties() {
- ElideStandaloneAsyncSettings asyncProperties = new ElideStandaloneAsyncSettings() {
- @Override
- public Integer getThreadSize() {
- return 10;
- }
-
- @Override
- public Duration getQueryMaxRunTime() {
- return Duration.ofSeconds(120L);
- }
-
- @Override
- public Duration getQueryRetentionDuration() {
- return Duration.ofDays(10L);
- }
-
- @Override
- public Duration getQueryCancellationCheckInterval() {
- return Duration.ofSeconds(600L);
- }
-
- @Override
- public Duration getMaxAsyncAfter() {
- return Duration.ofSeconds(30L);
- }
-
- @Override
- public String getExportApiPathSpec() {
- return "/export/*";
- }
-
- @Override
- boolean enableExport() {
- return false;
- }
-
- @Override
- public boolean appendFileExtension() {
- return true;
- }
-
- @Override
- public boolean csvWriteHeader() {
- return false;
- }
-
- @Override
- public String getStorageDestination() {
- return "/tmp";
- }
-
- @Override
- public Duration getExportAsyncResponseTimeout() {
- return Duration.ofSeconds(30L);
- }
-
- @Override
- public ExecutorService getExportAsyncResponseExecutor() {
- return enableExport() ? Executors.newFixedThreadPool(getThreadSize() == null ? 6 : getThreadSize()) : null;
- }
- }
- return asyncProperties;
- }
- }
- ```
-
-
-
-
-### Running
-
-After configuring and starting our service, the following commands illustrate how to make asynchronous requests. Don't
-forget to change `localhost:8080` accordingly. The example below makes use of the models and sample data that the
-liquibase migrations added through our example is [available here][elide-demo].
-
-#### Submitting query
-
-
-
- ```curl
- curl -X POST http://localhost:8080/api/v1/asyncQuery/ \
- -H"Content-Type: application/vnd.api+json" -H"Accept: application/vnd.api+json" \
- -d'{
- "data": {
- "type": "asyncQuery",
- "id": "ba31ca4e-ed8f-4be0-a0f3-12088fa9263d",
- "attributes": {
- "query": "/group?sort=commonName&fields%5Bgroup%5D=commonName,description",
- "queryType": "JSONAPI_V1_0",
- "status": "QUEUED"
- }
- }
- }'
- ```
-
-
- ```curl
- curl -g -X POST -H"Content-Type: application/json" \
- -H"Accept: application/json" "http://localhost:8080/graphql/api/v1" \
- -d'{
- "query" : "mutation { asyncQuery(op: UPSERT, data: {id: \"bb31ca4e-ed8f-4be0-a0f3-12088fb9263e\", query: \"{\\\"query\\\":\\\"{ group { edges { node { name } } } }\\\",\\\"variables\\\":null}\", queryType: GRAPHQL_V1_0, status: QUEUED}) { edges { node { id query queryType status result {completedOn responseBody contentLength httpStatus recordCount} } } } }"
- }'
- ```
-
-
- ```curl
- curl -X POST http://localhost:8080/api/v1/tableExport/ \
- -H"Content-Type: application/vnd.api+json" -H"Accept: application/vnd.api+json" \
- -d'{
- "data": {
- "type": "tableExport",
- "id": "ba31ca4e-ed8f-4be0-a0f3-12088fa9263f",
- "attributes": {
- "query": "/group?sort=commonName&fields%5Bgroup%5D=commonName,description",
- "queryType": "JSONAPI_V1_0",
- "status": "QUEUED",
- "resultType": "CSV"
- }
- }
- }'
- ```
-
-
- ```curl
- curl -g -X POST -H"Content-Type: application/json" \
- -H"Accept: application/json" "http://localhost:8080/graphql/api/v1" \
- -d'{
- "query" : "mutation { tableExport(op: UPSERT, data: {id: \"bb31ca4e-ed8f-4be0-a0f3-12088fb9263g\", query: \"{\\\"query\\\":\\\"{ group { edges { node { name } } } }\\\",\\\"variables\\\":null}\", queryType: GRAPHQL_V1_0, resultType: CSV, status: QUEUED}) { edges { node { id query queryType resultType status result {completedOn url message httpStatus recordCount} } } } }"
- }'
- ```
-
-
-
-Here are the respective responses:
-
-
-
- ```json
- {
- "data":{
- "type":"asyncQuery",
- "id":"ba31ca4e-ed8f-4be0-a0f3-12088fa9263d",
- "attributes":{
- "asyncAfterSeconds":10,
- "principalName":null,
- "createdOn":"2020-04-08T23:29Z",
- "query":"/group?sort=commonName&fields%5Bgroup%5D=commonName,description",
- "queryType":"JSONAPI_V1_0",
- "status":"COMPLETE",
- "updatedOn":"2020-04-08T23:29Z",
- "result":{
- "recordCount":2,
- "httpStatus":200,
- "completedOn":"2020-04-08T23:29Z",
- "contentLength":282,
- "responseBody":"{\"data\":[{\"type\":\"group\",\"id\":\"com.paiondata.elide\",\"attributes\":{\"commonName\":\"Elide\",\"description\":\"The magical library powering this project\"}},{\"type\":\"group\",\"id\":\"com.example.repository\",\"attributes\":{\"commonName\":\"Example Repository\",\"description\":\"The code for this project\"}}]}"
- }
- }
- }
- }
- ```
-
-
- ```json
- {
- "data":{
- "asyncQuery":{
- "edges":[
- {
- "node":{
- "id":"bb31ca4e-ed8f-4be0-a0f3-12088fb9263e",
- "query":"{\"query\":\"{ group { edges { node { name } } } }\",\"variables\":null}",
- "queryType":"GRAPHQL_V1_0",
- "status":"COMPLETE",
- "result":{
- "completedOn":"2020-04-08T21:25Z",
- "responseBody":"{\"data\":{\"group\":{\"edges\":[{\"node\":{\"name\":\"com.example.repository\"}},{\"node\":{\"name\":\"com.paiondata.elide\"}},{\"node\":{\"name\":\"elide-demo\"}}]}}}",
- "contentLength":109,
- "httpStatus":200,
- "recordCount":2
- }
- }
- }
- ]
- }
- }
- }
- ```
-
-
- ```json
- {
- "data":{
- "type":"tableExport",
- "id":"ba31ca4e-ed8f-4be0-a0f3-12088fa9263f",
- "attributes":{
- "asyncAfterSeconds":10,
- "principalName":null,
- "createdOn":"2020-04-08T23:29Z",
- "query":"/group?sort=commonName&fields%5Bgroup%5D=commonName,description",
- "queryType":"JSONAPI_V1_0",
- "resultType":"CSV",
- "status":"COMPLETE",
- "updatedOn":"2020-04-08T23:29Z",
- "result":{
- "recordCount":2,
- "httpStatus":200,
- "completedOn":"2020-04-08T23:29Z",
- "url":"http://localhost:8080/export/ba31ca4e-ed8f-4be0-a0f3-12088fa9263f",
- "message":null
- }
- }
- }
- }
- ```
-
-
- ```json
- {
- "data":{
- "asyncQuery":{
- "edges":[
- {
- "node":{
- "id":"bb31ca4e-ed8f-4be0-a0f3-12088fb9263g",
- "query":"{\"query\":\"{ group { edges { node { name } } } }\",\"variables\":null}",
- "queryType":"GRAPHQL_V1_0",
- "resultType":"CSV",
- "status":"COMPLETE",
- "result":{
- "completedOn":"2020-04-08T21:25Z",
- "url":"http://localhost:8080/export/bb31ca4e-ed8f-4be0-a0f3-12088fb9263g",
- "message":null,
- "httpStatus":200,
- "recordCount":2
- }
- }
- }
- ]
- }
- }
- }
- ```
-
-
-
-### Retrieving status and result
-
-Long-running queries in the QUEUED or PROCESSING state may not return with the `result` property populated in the
-responses above. The client can poll the AsyncQuery and TableExport objects asynchronously for status updates.
-
-
-
- ```curl
- curl -X GET http://localhost:8080/api/v1/asyncQuery/ba31ca4e-ed8f-4be0-a0f3-12088fa9263d \
- -H"Content-Type: application/vnd.api+json" -H"Accept: application/vnd.api+json"
- ```
-
-
- ```curl
- curl -g -X POST -H"Content-Type: application/json" -H"Accept: application/json" \
- "http://localhost:8080/graphql/api/v1" \
- -d'{
- "query" : "{ asyncQuery (ids: \"bb31ca4e-ed8f-4be0-a0f3-12088fb9263e\") { edges { node { id query queryType status result {completedOn responseBody contentLength httpStatus recordCount}} } } }"
- }'
- ```
-
-
- ```curl
- curl -X GET http://localhost:8080/api/v1/tableExport/ba31ca4e-ed8f-4be0-a0f3-12088fa9263f \
- -H"Content-Type: application/vnd.api+json" -H"Accept: application/vnd.api+json"
- ```
-
-
- ```curl
- curl -g -X POST -H"Content-Type: application/json" -H"Accept: application/json" \
- "http://localhost:8080/graphql/api/v1" \
- -d'{
- "query" : "{ tableExport (ids: \"bb31ca4e-ed8f-4be0-a0f3-12088fb9263g\") { edges { node { id query queryType resultType status result {completedOn url message httpStatus recordCount}} } } }"
- }'
- ```
-
-
-
-Here are the respective responses:
-
-
-
- ```json
- {
- "data":{
- "type":"asyncQuery",
- "id":"ba31ca4e-ed8f-4be0-a0f3-12088fa9263d",
- "attributes":{
- "asyncAfterSeconds":10,
- "principalName":null,
- "createdOn":"2020-04-08T21:25Z",
- "query":"/group?sort=commonName&fields%5Bgroup%5D=commonName,description",
- "queryType":"JSONAPI_V1_0",
- "status":"COMPLETE",
- "updatedOn":"2020-04-08T21:25Z",
- "result":{
- "recordCount":2,
- "httpStatus":200,
- "completedOn":"2020-04-08T23:29Z",
- "contentLength":282,
- "responseBody":"{\"data\":[{\"type\":\"group\",\"id\":\"com.paiondata.elide\",\"attributes\":{\"commonName\":\"Elide\",\"description\":\"The magical library powering this project\"}},{\"type\":\"group\",\"id\":\"com.example.repository\",\"attributes\":{\"commonName\":\"Example Repository\",\"description\":\"The code for this project\"}}]}"
- }
- }
- }
- }
- ```
-
-
- ```json
- {
- "data":{
- "asyncQuery":{
- "edges":[
- {
- "node":{
- "id":"bb31ca4e-ed8f-4be0-a0f3-12088fb9263e",
- "query":"{\"query\":\"{ group { edges { node { name } } } }\",\"variables\":null}",
- "queryType":"GRAPHQL_V1_0",
- "status":"COMPLETE",
- "result":{
- "completedOn":"2020-04-08T21:25Z",
- "responseBody":"{\"data\":{\"group\":{\"edges\":[{\"node\":{\"name\":\"com.example.repository\"}},{\"node\":{\"name\":\"com.paiondata.elide\"}},{\"node\":{\"name\":\"elide-demo\"}}]}}}",
- "contentLength":109,
- "httpStatus":200,
- "recordCount":2
- }
- }
- }
- ]
- }
- }
- }
- ```
-
-
- ```json
- {
- "data":{
- "type":"tableExport",
- "id":"ba31ca4e-ed8f-4be0-a0f3-12088fa9263f",
- "attributes":{
- "asyncAfterSeconds":10,
- "principalName":null,
- "createdOn":"2020-04-08T21:25Z",
- "query":"/group?sort=commonName&fields%5Bgroup%5D=commonName,description",
- "queryType":"JSONAPI_V1_0",
- "resultType":"CSV",
- "status":"COMPLETE",
- "updatedOn":"2020-04-08T21:25Z",
- "result":{
- "recordCount":2,
- "httpStatus":200,
- "completedOn":"2020-04-08T23:29Z",
- "url":"http://localhost:8080/export/ba31ca4e-ed8f-4be0-a0f3-12088fa9263f",
- "message":null
- }
- }
- }
- }
- ```
-
-
- ```json
- {
- "data":{
- "asyncQuery":{
- "edges":[
- {
- "node":{
- "id":"bb31ca4e-ed8f-4be0-a0f3-12088fb9263e",
- "query":"{\"query\":\"{ group { edges { node { name } } } }\",\"variables\":null}",
- "queryType":"GRAPHQL_V1_0",
- "resultType":"CSV",
- "status":"COMPLETE",
- "result":{
- "completedOn":"2020-04-08T21:25Z",
- "url":"http://localhost:8080/export/bb31ca4e-ed8f-4be0-a0f3-12088fb9263g",
- "message":null,
- "httpStatus":200,
- "recordCount":2
- }
- }
- }
- ]
- }
- }
- }
- ```
-
-
-
-### Downloading the TableExport results
-
-The TableExport request will return a URL to download the results as shown in the example response below.
-
-
-
- ```json
- {
- "result":{
- "completedOn":"2020-04-08T21:25Z",
- "url":"http://localhost:8080/export/bb31ca4e-ed8f-4be0-a0f3-12088fb9263g",
- "message":null,
- "httpStatus":200,
- "recordCount":2
- }
- }
- ```
-
-
- ```json
- [
- {
- "commonName":"Elide",
- "description":"The magical library powering this project"
- },
- {
- "commonName":"Example Repository",
- "description":"The code for this project"
- }
- ]
- ```
-
-
- ```csv
- "commonName", "description"
- "Elide", "The magical library powering this project"
- "Example Repository", "The code for this project"
- ```
-
-
-
-Overriding the AsyncApiDao
---------------------------
-
-The Async API interacts with the persistence layer through an abstraction - the AsyncApiDao, for status updates, query
-cleanup, etc. This can be customized by providing our own implementation. Elide provides a default implementation of
-[AsyncApiDao][default-async-api-dao].
-
-
-
-
- Create a `@Configuration` class that defines our custom implementation as a `@Bean`.
-
- ```java
- @Configuration
- public class ElideConfiguration {
- /**
- * Configure the AsyncApiDao used by async requests.
- * @return an AsyncApiDao object.
- */
- @Bean
- public AsyncApiDao asyncApiDao() {
- return new CustomAsyncApiDao();
- }
- }
- ```
-
-
-
-
- Override ElideStandaloneSettings.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAsyncSettings getAsyncProperties() {
- ElideStandaloneAsyncSettings asyncProperties = new ElideStandaloneAsyncSettings() {
- /**
- * Implementation of AsyncApiDao to use.
- * @return AsyncApiDao type object.
- */
- @Override
- public AsyncApiDao getAsyncApiDao() {
- return new CustomAsyncApiDao();
- }
- }
- return asyncProperties;
- }
- }
- ```
-
-
-
-
-Overriding the ResultStorageEngine
-----------------------------------
-
-Table exports leverage a reactive abstraction (ResultStorageEngine) for streaming results to and from a persistence
-backend. This can be customized by providing our own implementation. Elide provides default implementation of
-[ResultStorageEngine][file-result-storage-engine].
-
-
-
-
- Create a `@Configuration` class that defines our custom implementation as a `@Bean`.
-
- ```java
- @Configuration
- public class ElideConfiguration {
- /**
- * Configure the ResultStorageEngine used by TableExport requests.
- * @return a ResultStorageEngine object.
- */
- @Bean
- public ResultStorageEngine resultStorageEngine() {
- return new CustomResultStorageEngine();
- }
- }
- ```
-
-
-
-
- Override ElideStandaloneSettings.
-
- ```java
- public abstract class Settings implements ElideStandaloneSettings {
- @Override
- public ElideStandaloneAsyncSettings getAsyncProperties() {
- ElideStandaloneAsyncSettings asyncProperties = new ElideStandaloneAsyncSettings() {
- /**
- * Implementation of ResultStorageEngine to use.
- * @return ResultStorageEngine type object.
- */
- @Override
- public ResultStorageEngine getResultStorageEngine() {
- return new CustomResultStorageEngine();
- }
- }
- return asyncProperties;
- }
- }
- ```
-
-
-
-
-Internals
----------
-
-![Error loading elide-async-design.png](img/elide-async-design.png)
-
-[demo-schema]: asyncapi#enable-the-async-api
-[file-result-storage-engine]: https://github.com/paion-data/elide/blob/master/elide-async/src/main/java/com/paiondata/elide/async/service/storageengine/FileResultStorageEngine.java
-[default-async-api-dao]: https://github.com/paion-data/elide/blob/master/elide-async/src/main/java/com/paiondata/elide/async/service/dao/DefaultAsyncApiDao.java
-[elide-demo]: asyncapi#running
diff --git a/docs/docs/crud/elide/clientapis/graphql-federation.md b/docs/docs/crud/elide/clientapis/graphql-federation.md
deleted file mode 100644
index 2cdc0a7a..00000000
--- a/docs/docs/crud/elide/clientapis/graphql-federation.md
+++ /dev/null
@@ -1,287 +0,0 @@
----
-sidebar_position: 5
-title: GraphQL Federation
-description: Beta support for GraphQL Federation
----
-
-What is GraphQL Federation
---------------------------
-
-GraphQL Federation lets us declaratively combine multiple GraphQL APIs into a single, federated graph. This federated
-graph enables clients to interact with multiple APIs through a single request.
-
-A client makes a request to the single entry point of the federated graph called the router. The router intelligently
-orchestrates and distributes the request across your APIs and returns a unified response. For a client, the request and
-response cycle of querying the router looks the same as querying any GraphQL server.
-
-![Error loading graphql-federation.png](img/graphql-federation.png)
-
-### Benefits of Federation
-
-#### Microservices Architecture
-
-GraphQL Federation lets API teams operate in a
-[microservices architecture](https://www.atlassian.com/microservices/microservices-architecture/microservices-vs-monolith)
-while exposing a unified GraphQL API to clients. Understanding these concepts can help us get the most out of
-federation.
-
-#### Preserve Client Simplicity and Performance
-
-A client may need to make multiple requests when interacting with multiple non-federated GraphQL APIs. This can happen
-when an organization adopting GraphQL has multiple teams developing APIs independently. Each team sets up a GraphQL API
-that provides the data used by that team. For example, a travel app may have separate GraphQL APIs for users, flights,
-and hotels:
-
-![Error loading multiple-graphql-apis.png](img/multiple-graphql-apis.png)
-
-With a single federated graph, we preserve a powerful advantage of GraphQL over traditional REST APIs: the ability to
-fetch all the data we need in a single request.
-
-![Error loading multiple-graphql-apis-federated.png](img/multiple-graphql-apis-federated.png)
-
-The router intelligently calls all the APIs it needs to complete requests rather than simply forwarding them. For
-performance and security reasons, clients should only query the router, and only the router should query the constituent
-APIs. No client-side configuration is required.
-
-#### Design Schemas at Scale
-
-Some alternative approaches to combining GraphQL APIs impose limits on our schema, like adding namespaces or
-representing relationships with IDs instead of types. With these approaches, our individual GraphQL API schemas may look
-unchanged—but the resulting federated schema that clients interact with is more complex. Subsequently, it requires us to
-make frontend as well as backend changes.
-
-With GraphQL Federation, clients can interact with the federated schema as if it were a monolith. Consumers of our API
-shouldn't know or care that it's implemented as microservices.
-
-GraphQL Federation in Elide
----------------------------
-
-Elide supports GraphQL Federation. This feature needs to be enabled to be used.
-
-### Enabling GraphQL Federation
-
-```yaml
-elide:
- graphql:
- federation:
- enabled: true
-```
-
-### Schema Introspection Queries
-
-When GraphQL Federation is enabled, Elide will respond to enhanced introspection queries with `Query._service` with the
-GraphQL schemas generated by Elide.
-
-```graphql
-query {
- _service {
- sdl
- }
-}
-```
-
-Elide does not have any built in measures to control which clients can execute the introspection queries. These queries
-should typically be restricted only to the federated graph routers.
-
-### Implementing Federated Graphs
-
-Elide generates its GraphQL schema programatically and cannot be used to define federated entities.
-
-This will need to be done in another subgraph implementation using a different subgraph library, for instance Spring
-GraphQL.
-
-#### Extending an Elide entity
-
-The Elide entity can be extended with additional entities from the subgraph using the `@extends` directive. The
-configurations are done in the subgraph and not in Elide.
-
-In the following example the `Group` entity from Elide is being extended to provide the additional `GroupReview` entity
-provided by the subgraph.
-
-```graphql
-type Group @key(fields: "name") @extends {
- name: DeferredID! @external
- groupReviews: [GroupReview!]!
-}
-```
-
-Note that Elide uses a custom scalar `DeferredID` instead of `ID` which will need to be registered with the subgraph.
-
-The following query is an example that starts from the `Group` entity on Elide and references the `GroupReview` entity
-on the subgraph.
-
-```graphql
-query {
- group {
- edges {
- node {
- commonName
- groupReviews {
- stars
- text
- }
- }
- }
- }
-}
-```
-
-After the router queries the `Group` entity on Elide, it will also make another query to this subgraph to get the
-`GroupReview` entity.
-
-The router will use the following query on the subgraph to add the additional fields of `GroupReview` to the `Group`
-entity.
-
-```graphql
-query {
- _entities(representations: [{__typename: "Group", name: "com.paiondata.elide"}]) {
- ... on Group {
- stars
- text
- }
- }
-}
-```
-
-For Spring GraphQL the representations can be configured as shown below.
-
-The mapping for the representations to the `Group` is configured in the entity data fetcher for instance in
-`com.example.reviews.config.GraphQLConfiguration`.
-
-```java
-DataFetcher> entityDataFetcher = env -> {
- List