XWiki GraphQL API

Last modified by Admin on 2021/09/06 00:23

cogGraphQL API to expose the XWiki model for usecases where REST is too verbose or produces too much back and forward between the client and the server.
TypeJAR
Category
Developed by

Eduard Moraru, Simon Urli

Active Installs0
Rating
0 Votes
LicenseGNU Lesser General Public License 2.1
Compatibility

XWiki 12.6+

Installable with the Extension Manager

Description

This is currently a proof of concept and not yet ready for production.

Inspiration: https://github.com/phillip-kruger/graphql-example

It follows the Eclipse MicroProfile GraphQL API Specifications using the SmallRye GraphQL Implementation. It uses the code-first approach, rather than the schema-first traditional approach, and extracts the schema from the annotated model classes and methods defined in the code.

The API is accessible through the /graphql endpoint, implemented using the XWiki Reference API

The generated schema can be obtained by getting the following URL:

http://localhost:8080/xwiki/graphql/schema.graphql

A very basic query (in this case, the title and content for the Main.WebHome document) can be done by using curl:

curl -X POST -H "Content-Type: application/json" -d '{"query": "{ document(documentReference: \"Main.WebHome\") { title, content }}"}' http://localhost:8080/xwiki/graphql

One recommended client for experimenting with the API inside your browser would be the Altair Firefox add-on.

Authentication

The standard XWiki authentication methods are supported, similar to the REST API:

  • HTTP Basic Authentication
  • XWiki Session (browser session)

Model

For the moment, the XWiki model is based on the com.xpn.xwiki.api.* classes and a jandex index is provided for these as part of the api jar.

For the future, it may be a good idea to explicitly define an XWiki model (or a supported subset of it) and not depend on the com.xpn.xwiki.api.* classes since they bring a lot of unnecessary noise.

Queries

Examples of currently supported basic CRUD queries on the XWiki model:

  • Get (only) the specified document fields, objects and attachments:
    {
      document(documentReference: "Sandbox.WebHome") {
        title
        author
        creationDate
        contentUpdateDate
        syntax
        content
        objects {
          reference
        }
        attachmentList {
          filename
          date
        }
      }
    }
    • Output:
      {
        "data": {
          "document": {
            "title": "Sandbox",
            "author": "XWiki.superadmin",
            "creationDate": "2020-07-31T17:12:50",
            "contentUpdateDate": "2020-07-31T17:12:50",
            "syntax": "XWiki 2.1",
            "content": "The sandbox is a part of your wiki that you can freely modify. It's meant to let you practice editing. You will ...",
            "objects": [],
            "attachmentList": [
              {
                "filename": "XWikiLogo.png",
                "date": "2020-07-31T17:12:50"
              }
            ]
          }
        }
      }
  • Get all documents, but only the specified fields:
    {
      documents {
        documentReference
        pageReference
        title
        author
      }
    }
    • Output:
      {
        "data": {
          "documents": [
            {
              "documentReference": "xwiki:WikiManager.Translations",
              "pageReference": "xwiki:WikiManager/Translations",
              "title": "Translations",
              "author": "XWiki.superadmin"
            },
            {
              "documentReference": "xwiki:XWiki.DocumentTreeTranslations",
              "pageReference": "xwiki:XWiki/DocumentTreeTranslations",
              "title": "Document Tree Translations",
              "author": "XWiki.superadmin"
            },
            {
              "documentReference": "xwiki:XWiki.Notifications.Code.EmailJobClass",
              "pageReference": "xwiki:XWiki/Notifications/Code/EmailJobClass",
              "title": "EmailJobClass",
              "author": "XWiki.superadmin"
            },
            {
              "documentReference": "xwiki:AppWithinMinutes.TextArea",
              "pageReference": "xwiki:AppWithinMinutes/TextArea",
              "title": "$services.localization.render('platform.appwithinminutes.classEditorTextAreaFieldName')",
              "author": "XWiki.superadmin"
            },
      ...
  • Mixing both individual and all documents requests within the same query:
    {
      document(documentReference: "Main.WebHome") {
        title
        author
        creationDate
        contentUpdateDate
        syntax
        content
      }
      documents {
        title
      }
    }
    • Output:
      {
        "data": {
          "document": {
            "title": "Home",
            "author": "XWiki.superadmin",
            "creationDate": "2020-07-31T17:13:06",
            "contentUpdateDate": "2020-07-31T17:13:06",
            "syntax": "XWiki 2.1",
            "content": "{{box cssClass=\"floatinginfobox\"}}\n{{velocity}}\n{{html clean=\"false\"}}\n## ..."
          },
          "documents": [
            {
              "title": "Translations"
            },
            {
              "title": "Document Tree Translations"
            },
            {
              "title": "EmailJobClass"
            },
      ...
  • Create or update a document:
    mutation {
      createOrUpdateDocument(
        documentReference: "Sandbox.NewDocument"
        title: "Document Title"
        content: "Hello2!"
      ) {
        documentReference
      }
    }
    • Output:
      {
        "data": {
          "createOrUpdateDocument": {
            "documentReference": "xwiki:Sandbox.NewDocument"
          }
        }
      }
  • Delete a document:
    mutation {
      deleteDocument(documentReference: "Sandbox.NewDocument") {
        documentReference
      }
    }
    • Ouput:
      {
        "data": {
          "deleteDocument": {
            "documentReference": "xwiki:Sandbox.NewDocument"
          }
        }
      }

More details on the current API and the supported queries and mutations can be found either in the above mentioned generated schema, or in the code.

Extending the model

The API can be easily extended by any XWiki extension that comes with a pre-built jandex index containing the extra model classes together with an entry point annotated with the @GraphQLApi (org.eclipse.microprofile.graphql.GraphQLApi) java annotation. Such an index can be precomputed at build time using the jandex maven plugin.

To better demonstrate how this can be done, a sample project has been provided that contains a sample POJO API supporting basic CRUD operations on a Basket class containing a list of Fruit subclasses.

The entry point of the sample project is the MyFruitBasketGraphQlApi class that is annotated with @GraphQLApi and that exposes one @Query method to get the basket of fruits and two @Mutation methods to add or remove fruits to/from the basket.

The sample project's pom.xml depends on the api-graphql module and indexes the sample project's classes at build time with the jandex-maven-plugin.

Building this sample extension and installing it at the farm level (similarly to the api-graphql extension) will allow you to perform queries such as:

  • Getting the fruits from the basket:
    {
      basket {
        fruits {
          name
          color
        }
      }
    }
    • Output:
      {
        "data": {
          "basket": {
            "fruits": [
              {
                "name": "apple",
                "color": "red"
              },
              {
                "name": "apple",
                "color": "red"
              },
              {
                "name": "orange",
                "color": "orange"
              },
              {
                "name": "banana",
                "color": "yellow"
              }
            ]
          }
        }
      }
  • Adding a fruit to the basket:
    mutation{
      addFruit(type: "Orange") {
        color
        name
      }
    }
    • Output:
      {
        "data": {
          "addFruit": {
            "color": "orange",
            "name": "orange"
          }
        }
      }
  • Removing a fruit from the basket:
    mutation{
      removeFruit(index: 0){
        name
        color
      }
    }
    • Output:
      {
        "data": {
          "removeFruit": {
            "name": "apple",
            "color": "red"
          }
        }
      }

Current Limitations and TODO

  • XWikiDocument contains 2 getters returning void: XWikiDocument.getUniqueLinkedEntityReferences (2 signatures), causing issues when attempted to be queried
  • We can not use @Inject-ed XWiki components inside GraphqlApi (or any other @GraphQLApi annotated entry-point class) since its lifecycle is managed by the graphql library, which is not aware of XWiki components. The only solution is to use Utils.getComponent.
  • At each request, the schema is reloaded and re-initialized. The advantage is that any installed extension will be easily picked up and the model will be extended. The disadvantage is the performance impact caused by not reusing an already initialized schema and only reloading it when changes are done (i.e. extension installed/uninstalled).

Prerequisites & Installation Instructions

Before XWiki 12.7 (not needed for 12.7+):

  • Delete the existing antlr4-runtime-4.5.1-1.jar core extension (located in the webapps/xwiki/WEB-INF/libs folder) and replace it with a version greater than or equal to 4.7.2 (as required by the graphql-java dependency). 
  • Restart XWiki and install the GraphQL API extension.

Release Notes

v0.2

v0.1

First version.

  • Hardcoded model
  • No user authentication (always using "XWiki.Admin")

Dependencies

Dependencies for this extension (org.xwiki.contrib:api-graphql 0.2):

  • org.xwiki.commons:xwiki-commons-component-default 12.4
  • org.xwiki.platform:xwiki-platform-oldcore 12.4
  • org.xwiki.platform:xwiki-platform-query-manager 12.4
  • io.smallrye:smallrye-graphql 1.0.7
  • io.smallrye:smallrye-graphql-schema-builder 1.0.7
  • org.xwiki.platform:xwiki-platform-container-servlet 12.4
  • jakarta.json:jakarta.json-api 1.1.6
  • org.glassfish:jakarta.json 1.1.6
  • jakarta.json.bind:jakarta.json.bind-api 1.0.2
  • org.eclipse:yasson 1.0.7

Get Connected