diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 2260a93d7a..0185021fc4 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -6,6 +6,7 @@ . link:topics/custom-attributes.adoc[Custom User Attributes] {% if book.community %} . link:topics/providers.adoc[Service Provider Interfaces (SPI)] + . link:topics/extensions.adoc[Extending Server] . link:topics/auth-spi.adoc[Authentication SPI] . link:topics/events.adoc[Event Listener SPI] . link:topics/user-federation.adoc[User Federation SPI] diff --git a/topics/extensions.adoc b/topics/extensions.adoc new file mode 100644 index 0000000000..5292dd4eec --- /dev/null +++ b/topics/extensions.adoc @@ -0,0 +1,147 @@ +[[_extensions]] + +== Extending the Server + +The {{book.project.name}} SPI framework offers the possibility to implement or override particular built-in providers. However {{book.project.name}} +also provides capabilities to extend it's core functionalities and domain. This includes possibilities to: + +* Add custom REST endpoints to the {{book.project.name}} server +* Add your own custom SPI +* Add custom JPA entities to the {{book.project.name}} data model + +[[_extensions_rest]] +=== Add custom REST endpoints + +This is a very powerful extension, which allows you to deploy your own REST endpoints to the {{book.project.name}} server. It enables all kinds of extensions, for example +the possibility to trigger functionality on the {{book.project.name}} server, which is not available through the default set of built-in {{book.project.name}} REST endpoints. + +To add a custom REST endpoint, you need to implement the `RealmResourceProviderFactory` and `RealmResourceProvider` interfaces. `RealmResourceProvider` has one important method: + +[source,java] +---- + +Object getResource(); + +---- + +which allows you to return an object, which acts as a https://jax-rs-spec.java.net/[JAX-RS Resource]. For more details, see the Javadoc and our examples. +There is a very simple example in the example distribution in `providers/rest` and there is a more advanced example in `providers/domain-extension`, +which shows how to add an authenticated REST endpoint and other functionalities like <> +or <>. + +For details on how to package and deploy a custom provider, refer to the <> chapter. + +[[_extensions_spi]] +=== Add your own custom SPI + +This is useful especially with the <>. To add your own kind of SPI, you need to +implement the interface `org.keycloak.provider.Spi` and define the ID of your SPI and the `ProviderFactory` and `Provider` classes. That looks like this: + +[source,java] +---- +package org.keycloak.examples.domainextension.spi; + +import ... + +public class ExampleSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "example"; + } + + @Override + public Class getProviderClass() { + return ExampleService.class; + } + + @Override + @SuppressWarnings("rawtypes") + public Class getProviderFactoryClass() { + return ExampleServiceProviderFactory.class; + } + +} + +---- + +Then you need to create the file `META-INF/services/org.keycloak.provider.Spi` and add the class of your SPI to it. For example: + +[source] +---- +org.keycloak.examples.domainextension.spi.ExampleSpi +---- + +The next step is to create the interfaces `ExampleServiceProviderFactory`, which extends from `ProviderFactory` and `ExampleService`, which extends from `Provider`. +The `ExampleService` will usually contain the business methods you need for your use case. Note that the `ExampleServiceProviderFactory` instance +is always scoped per application, however `ExampleService` is scoped per-request (or more accurately per `KeycloakSession` lifecycle). + +Finally you need to implement your providers in the same manner as described in the <> chapter. + +For more details, take a look at the example distribution at `providers/domain-extension`, which shows an Example SPI similar to the one above. + +[[_extensions_jpa]] +=== Add custom JPA entities to the {{book.project.name}} data model + +If the {{book.project.name}} data model does not exactly match your desired solution, or if you want to add some core functionality to {{book.project.name}}, +or when you have your own REST endpoint, you might want to extend the {{book.project.name}} data model. We enable you to add your +own JPA entities to the {{book.project.name}} JPA `EntityManager` . + +To add your own JPA entities, you need to implement `JpaEntityProviderFactory` and `JpaEntityProvider`. The `JpaEntityProvider` +allows you to return a list of your custom JPA entities and provide the location and id of the liquibase changelog. An example implementation can look like this: + +[source,java] +---- +public class ExampleJpaEntityProvider implements JpaEntityProvider { + + // List of your JPA entities. + @Override + public List> getEntities() { + return Collections.>singletonList(Company.class); + } + + // This is used to return the location of the Liquibase changelog file. + // You can return null if you don't want Liquibase to create and update the DB schema. + @Override + public String getChangelogLocation() { + return "META-INF/example-changelog.xml"; + } + + // Helper method, which will be used internally by Liquibase. + @Override + public String getFactoryId() { + return "sample"; + } + + ... +} +---- + +In the example above, we added a single JPA entity represented by class `Company`. In the code of your REST endpoint, you can then use something like +this to retrieve `EntityManager` and call DB operations on it. + + +[source,java] +---- +EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager(); +Company myCompany = em.find(Company.class, "123"); +---- + +The methods `getChangelogLocation` and `getFactoryId` are important to support automatic updating of your entities by Liquibase. http://www.liquibase.org/[Liquibase] +is a framework for updating the database schema, which {{book.project.name}} internally uses to create the DB schema and update the DB schema among versions. You may need to use +it as well and create a changelog for your entities. Note that versioning of your own liquibase changelog is independent +of {{book.project.name}} versions. In other words, when you update to a new {{book.project.name}} version, you are not forced to update your +schema at the same time. And vice versa, you can update your schema even without updating the {{book.project.name}} version. The Liquibase update +is always done at the server startup, so to trigger a DB update of your schema, you just need to add the new changeset to your liquibase changelog file (in the example above +it's the file `META-INF/example-changelog.xml` (which must be packed in same JAR as the JPA entities and `ExampleJpaEntityProvider`) and then restart server. +The DB schema will be automatically updated at startup. + +For more details, take a look at the example distribution at example `providers/domain-extension`, which shows the `ExampleJpaEntityProvider` and `example-changelog.xml` described above. + +NOTE: Don't forget to always backup your database before doing any changes in the Liquibase changelog and triggering a DB update. +