KEYCLOAK-2474 Polishing. Support for separate changelock table per changelog. Support for authenticated endpoint in domain-extension example
This commit is contained in:
parent
44d7d776bc
commit
c4513fdad9
35 changed files with 368 additions and 664 deletions
|
@ -1 +0,0 @@
|
||||||
/target/
|
|
|
@ -3,7 +3,8 @@ Example Domain Extension
|
||||||
|
|
||||||
To run, deploy as a module by running:
|
To run, deploy as a module by running:
|
||||||
|
|
||||||
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api"
|
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist"
|
||||||
|
|
||||||
|
|
||||||
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
||||||
|
|
||||||
|
@ -12,4 +13,32 @@ Then registering the provider by editing keycloak-server.json and adding the mod
|
||||||
"module:org.keycloak.examples.domain-extension-example"
|
"module:org.keycloak.examples.domain-extension-example"
|
||||||
],
|
],
|
||||||
|
|
||||||
Then start (or restart) the server. Once started do xyz TODO.
|
Then start (or restart) the server.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
First you can create some example companies with these CURL requests.
|
||||||
|
|
||||||
|
````
|
||||||
|
curl -i --request POST http://localhost:8080/auth/realms/master/example/companies --data "{ \"name\": \"foo company\" }" --header "Content-type: application/json"
|
||||||
|
curl -i --request POST http://localhost:8080/auth/realms/master/example/companies --data "{ \"name\": \"bar company\" }" --header "Content-type: application/json"
|
||||||
|
````
|
||||||
|
|
||||||
|
Then you can lookup all companies
|
||||||
|
|
||||||
|
````
|
||||||
|
curl -i --request GET http://localhost:8080/auth/realms/master/example/companies --header "Accept: application/json"
|
||||||
|
````
|
||||||
|
|
||||||
|
If you create realm `foo` in Keycloak admin console and then replace the realm name in the URI (for example like `http://localhost:8080/auth/realms/foo/example/companies` ) you will see
|
||||||
|
that companies are scoped per-realm. So you will see different companies for realm `master` and for realm `foo` .
|
||||||
|
|
||||||
|
|
||||||
|
Testing with authenticated access
|
||||||
|
---------------------------------
|
||||||
|
Example contains the endpoint, which is accessible just for authenticated users. REST request must be authenticated with bearer access token
|
||||||
|
of authenticated user and the user must be in realm role `admin` in order to access the resource. You can run bash script from the current directory:
|
||||||
|
````
|
||||||
|
./invoke-authenticated.sh
|
||||||
|
````
|
||||||
|
The script assumes user `admin` with password `admin` exists in realm `master`. Also it assumes that you have `curl` installed.
|
19
examples/providers/domain-extension/invoke-authenticated.sh
Executable file
19
examples/providers/domain-extension/invoke-authenticated.sh
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export DIRECT_GRANT_RESPONSE=$(curl -i --request POST http://localhost:8080/auth/realms/master/protocol/openid-connect/token --header "Accept: application/json" --header "Content-Type: application/x-www-form-urlencoded" --data "grant_type=password&username=admin&password=admin&client_id=admin-cli")
|
||||||
|
|
||||||
|
echo -e "\n\nSENT RESOURCE-OWNER-PASSWORD-CREDENTIALS-REQUEST. OUTPUT IS:\n\n";
|
||||||
|
echo $DIRECT_GRANT_RESPONSE;
|
||||||
|
|
||||||
|
export ACCESS_TOKEN=$(echo $DIRECT_GRANT_RESPONSE | grep "access_token" | sed 's/.*\"access_token\":\"\([^\"]*\)\".*/\1/g');
|
||||||
|
echo -e "\n\nACCESS TOKEN IS \"$ACCESS_TOKEN\"";
|
||||||
|
|
||||||
|
echo -e "\n\nSENDING UN-AUTHENTICATED REQUEST. THIS SHOULD FAIL WITH 401: ";
|
||||||
|
curl -i --request POST http://localhost:8080/auth/realms/master/example/companies-auth --data "{ \"name\": \"auth foo company\" }" --header "Content-type: application/json"
|
||||||
|
|
||||||
|
echo -e "\n\nSENDING AUTHENTICATED REQUEST. THIS SHOULD SUCCESSFULY CREATE COMPANY AND SUCCESS WITH 201: ";
|
||||||
|
curl -i --request POST http://localhost:8080/auth/realms/master/example/companies-auth --data "{ \"name\": \"auth foo company\" }" --header "Content-type: application/json" --header "Authorization: Bearer $ACCESS_TOKEN";
|
||||||
|
|
||||||
|
echo -e "\n\nSEARCH COMPANIES: ";
|
||||||
|
curl -i --request GET http://localhost:8080/auth/realms/master/example/companies-auth --header "Accept: application/json" --header "Authorization: Bearer $ACCESS_TOKEN";
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.keycloak.examples.domainextension;
|
||||||
|
|
||||||
|
import org.keycloak.examples.domainextension.jpa.Company;
|
||||||
|
|
||||||
|
public class CompanyRepresentation {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public CompanyRepresentation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompanyRepresentation(Company company) {
|
||||||
|
id = company.getId();
|
||||||
|
name = company.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,110 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.entities;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.persistence.CascadeType;
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.NamedQueries;
|
|
||||||
import javax.persistence.NamedQuery;
|
|
||||||
import javax.persistence.OneToMany;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "EXAMPLE_COMPANY")
|
|
||||||
@NamedQueries({ @NamedQuery(name = "findAllCompanies", query = "from Company"),
|
|
||||||
@NamedQuery(name = "findByRealm", query = "from Company where realmId = :realmId") })
|
|
||||||
public class Company {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "ID")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Column(name = "NAME", nullable = false)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Column(name = "REALM_ID", nullable = false)
|
|
||||||
private String realmId;
|
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "company", orphanRemoval = true)
|
|
||||||
private final Set<Region> regions = new HashSet<>();
|
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "company", orphanRemoval = true)
|
|
||||||
private final Set<UserAccount> userAccounts = new HashSet<>();
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private Company() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Company(String realmId, String name) {
|
|
||||||
this.id = UUID.randomUUID().toString();
|
|
||||||
this.realmId = realmId;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealmId() {
|
|
||||||
return realmId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<UserAccount> getUserAccounts() {
|
|
||||||
return Collections.unmodifiableSet(userAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addUserAccount(UserAccount userAccount) {
|
|
||||||
userAccounts.add(userAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeUserAccount(UserAccount userAccount) {
|
|
||||||
return userAccounts.remove(userAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserAccount getUserAccountByUsername(String username) {
|
|
||||||
for (UserAccount userAccount : userAccounts) {
|
|
||||||
if (userAccount.getUser().getUsername().equals(username)) {
|
|
||||||
return userAccount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NoSuchElementException("No user found with name '" + username + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addRegion(Region region) {
|
|
||||||
regions.add(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeRegion(Region region) {
|
|
||||||
return regions.remove(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.entities;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
import org.keycloak.models.jpa.entities.UserEntity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Class UserAccount.
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
@Table(name = "EXAMPLE_USER_ACCOUNT")
|
|
||||||
public class UserAccount {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "ID")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "USER_ID", nullable = false)
|
|
||||||
private UserEntity user;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "COMPANY_ID", nullable = true)
|
|
||||||
private Company company;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private UserAccount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserAccount(String id, UserEntity userEntity, Company company) {
|
|
||||||
this.id = UUID.randomUUID().toString();
|
|
||||||
user = userEntity;
|
|
||||||
this.company = company;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserEntity getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Company getCompany() {
|
|
||||||
return company;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.entities;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "EXAMPLE_USER_ACCOUNT_REGION_ROLE")
|
|
||||||
public class UserAccountRegionRole {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "ID")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "USER_ACCOUNT_ID", nullable = false)
|
|
||||||
private UserAccount userAccount;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "REGION_ID", nullable = false)
|
|
||||||
private Region region;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "ROLE_ID", nullable = false)
|
|
||||||
private RoleEntity role;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private UserAccountRegionRole() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserAccountRegionRole(UserAccount userAccount, Region region, RoleEntity role) {
|
|
||||||
this.id = UUID.randomUUID().toString();
|
|
||||||
this.userAccount = userAccount;
|
|
||||||
this.region = region;
|
|
||||||
this.role = role;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserAccount getUserAccount() {
|
|
||||||
return userAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Region getRegion() {
|
|
||||||
return region;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RoleEntity getRole() {
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -15,21 +15,21 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.entities;
|
package org.keycloak.examples.domainextension.jpa;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "EXAMPLE_REGION")
|
@Table(name = "EXAMPLE_COMPANY")
|
||||||
public class Region {
|
@NamedQueries({ @NamedQuery(name = "findByRealm", query = "from Company where realmId = :realmId") })
|
||||||
|
public class Company {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "ID")
|
@Column(name = "ID")
|
||||||
private String id;
|
private String id;
|
||||||
|
@ -37,26 +37,30 @@ public class Region {
|
||||||
@Column(name = "NAME", nullable = false)
|
@Column(name = "NAME", nullable = false)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ManyToOne
|
@Column(name = "REALM_ID", nullable = false)
|
||||||
@JoinColumn(name = "COMPANY_ID", nullable = true)
|
private String realmId;
|
||||||
private Company company;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private Region() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Region(String name) {
|
|
||||||
this.id = UUID.randomUUID().toString();
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -15,16 +15,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.providers.entity;
|
package org.keycloak.examples.domainextension.jpa;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||||
import org.keycloak.examples.domainextension.entities.Company;
|
|
||||||
import org.keycloak.examples.domainextension.entities.Region;
|
|
||||||
import org.keycloak.examples.domainextension.entities.UserAccount;
|
|
||||||
import org.keycloak.examples.domainextension.entities.UserAccountRegionRole;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:erik.mulder@docdatapayments.com">Erik Mulder</a>
|
* @author <a href="mailto:erik.mulder@docdatapayments.com">Erik Mulder</a>
|
||||||
|
@ -35,16 +31,20 @@ public class ExampleJpaEntityProvider implements JpaEntityProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Class<?>> getEntities() {
|
public List<Class<?>> getEntities() {
|
||||||
return Arrays.asList(Company.class, Region.class, UserAccount.class, UserAccountRegionRole.class);
|
return Collections.<Class<?>>singletonList(Company.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getChangelogLocation() {
|
public String getChangelogLocation() {
|
||||||
return "example-changelog.xml";
|
return "META-INF/example-changelog.xml";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFactoryId() {
|
||||||
|
return ExampleJpaEntityProviderFactory.ID;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.providers.entity;
|
package org.keycloak.examples.domainextension.jpa;
|
||||||
|
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||||
|
@ -30,7 +30,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
||||||
*/
|
*/
|
||||||
public class ExampleJpaEntityProviderFactory implements JpaEntityProviderFactory {
|
public class ExampleJpaEntityProviderFactory implements JpaEntityProviderFactory {
|
||||||
|
|
||||||
private static final String ID = "example-entity-provider";
|
protected static final String ID = "example-entity-provider";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JpaEntityProvider create(KeycloakSession session) {
|
public JpaEntityProvider create(KeycloakSession session) {
|
|
@ -1,21 +1,24 @@
|
||||||
package org.keycloak.examples.domainextension.rest;
|
package org.keycloak.examples.domainextension.rest;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import org.keycloak.examples.domainextension.entities.Company;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.examples.domainextension.rest.model.CompanyView;
|
import org.keycloak.examples.domainextension.CompanyRepresentation;
|
||||||
import org.keycloak.examples.domainextension.services.ExampleService;
|
import org.keycloak.examples.domainextension.spi.ExampleService;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
public class CompanyResource {
|
public class CompanyResource {
|
||||||
|
|
||||||
private KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
|
||||||
public CompanyResource(KeycloakSession session) {
|
public CompanyResource(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
@ -23,19 +26,27 @@ public class CompanyResource {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("")
|
@Path("")
|
||||||
public Set<CompanyView> getMasterAccounts() {
|
@NoCache
|
||||||
List<Company> companies = session.getProvider(ExampleService.class).listCompanies();
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
Set<CompanyView> companyViews = new HashSet<>();
|
public List<CompanyRepresentation> getCompanies() {
|
||||||
for (Company company : companies) {
|
return session.getProvider(ExampleService.class).listCompanies();
|
||||||
companyViews.add(new CompanyView(company));
|
}
|
||||||
}
|
|
||||||
return companyViews;
|
@POST
|
||||||
|
@Path("")
|
||||||
|
@NoCache
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public Response createProviderInstance(CompanyRepresentation rep) {
|
||||||
|
session.getProvider(ExampleService.class).addCompany(rep);
|
||||||
|
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(rep.getId()).build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@NoCache
|
||||||
@Path("{id}")
|
@Path("{id}")
|
||||||
public CompanyView getCompany(@PathParam("id") final String id) {
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
return new CompanyView(session.getProvider(ExampleService.class).findCompany(id));
|
public CompanyRepresentation getCompany(@PathParam("id") final String id) {
|
||||||
|
return session.getProvider(ExampleService.class).findCompany(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -15,9 +15,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.providers.rest;
|
package org.keycloak.examples.domainextension.rest;
|
||||||
|
|
||||||
import org.keycloak.examples.domainextension.rest.ExampleRestResource;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.services.resource.RealmResourceProvider;
|
import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.providers.rest;
|
package org.keycloak.examples.domainextension.rest;
|
||||||
|
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
|
@ -1,15 +1,22 @@
|
||||||
package org.keycloak.examples.domainextension.rest;
|
package org.keycloak.examples.domainextension.rest;
|
||||||
|
|
||||||
|
import javax.ws.rs.ForbiddenException;
|
||||||
|
import javax.ws.rs.NotAuthorizedException;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.services.managers.AppAuthManager;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
|
||||||
public class ExampleRestResource {
|
public class ExampleRestResource {
|
||||||
|
|
||||||
private KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
private final AuthenticationManager.AuthResult auth;
|
||||||
|
|
||||||
public ExampleRestResource(KeycloakSession session) {
|
public ExampleRestResource(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
this.auth = new AppAuthManager().authenticateBearerToken(session, session.getContext().getRealm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("companies")
|
@Path("companies")
|
||||||
|
@ -17,4 +24,20 @@ public class ExampleRestResource {
|
||||||
return new CompanyResource(session);
|
return new CompanyResource(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same like "companies" endpoint, but REST endpoint is authenticated with Bearer token and user must be in realm role "admin"
|
||||||
|
// Just for illustration purposes
|
||||||
|
@Path("companies-auth")
|
||||||
|
public CompanyResource getCompanyResourceAuthenticated() {
|
||||||
|
checkRealmAdmin();
|
||||||
|
return new CompanyResource(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkRealmAdmin() {
|
||||||
|
if (auth == null) {
|
||||||
|
throw new NotAuthorizedException("Bearer");
|
||||||
|
} else if (auth.getToken().getRealmAccess() == null || !auth.getToken().getRealmAccess().isUserInRole("admin")) {
|
||||||
|
throw new ForbiddenException("Does not have realm admin role");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package org.keycloak.examples.domainextension.rest.model;
|
|
||||||
|
|
||||||
import org.keycloak.examples.domainextension.entities.Company;
|
|
||||||
|
|
||||||
public class CompanyView {
|
|
||||||
|
|
||||||
private String id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public CompanyView() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompanyView(Company company) {
|
|
||||||
id = company.getId();
|
|
||||||
name = company.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.services.repository;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
|
|
||||||
public abstract class AbstractRepository<T> {
|
|
||||||
|
|
||||||
private final EntityManager entityManager;
|
|
||||||
private final Class<T> clazz;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public AbstractRepository(EntityManager entityManager) {
|
|
||||||
this.entityManager = entityManager;
|
|
||||||
|
|
||||||
clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected EntityManager getEntityManager() {
|
|
||||||
return entityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T findById(String id) {
|
|
||||||
return entityManager.find(clazz, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove(T entity) {
|
|
||||||
entityManager.remove(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void persist(T entity) {
|
|
||||||
entityManager.persist(entity);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.services.repository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
|
|
||||||
import org.keycloak.examples.domainextension.entities.Company;
|
|
||||||
|
|
||||||
public class CompanyRepository extends AbstractRepository<Company> {
|
|
||||||
|
|
||||||
public CompanyRepository(EntityManager entityManager) {
|
|
||||||
super(entityManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Company> getAll() {
|
|
||||||
return getEntityManager().createNamedQuery("findAllCompanies", Company.class).getResultList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Company> getAll(String realmId) {
|
|
||||||
return getEntityManager().createNamedQuery("findByRealm", Company.class).setParameter("realmId", realmId)
|
|
||||||
.getResultList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -15,19 +15,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.services;
|
package org.keycloak.examples.domainextension.spi;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.examples.domainextension.entities.Company;
|
import org.keycloak.examples.domainextension.CompanyRepresentation;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
public interface ExampleService extends Provider {
|
public interface ExampleService extends Provider {
|
||||||
|
|
||||||
List<Company> listCompanies();
|
List<CompanyRepresentation> listCompanies();
|
||||||
|
|
||||||
Company findCompany(String id);
|
CompanyRepresentation findCompany(String id);
|
||||||
|
|
||||||
void addCompany(Company company);
|
CompanyRepresentation addCompany(CompanyRepresentation company);
|
||||||
|
|
||||||
}
|
}
|
|
@ -15,9 +15,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.services.spi;
|
package org.keycloak.examples.domainextension.spi;
|
||||||
|
|
||||||
import org.keycloak.examples.domainextension.services.ExampleService;
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
public interface ExampleServiceProviderFactory extends ProviderFactory<ExampleService> {
|
public interface ExampleServiceProviderFactory extends ProviderFactory<ExampleService> {
|
|
@ -15,9 +15,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.services.spi;
|
package org.keycloak.examples.domainextension.spi;
|
||||||
|
|
||||||
import org.keycloak.examples.domainextension.services.ExampleService;
|
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.provider.Spi;
|
import org.keycloak.provider.Spi;
|
|
@ -15,30 +15,30 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.services;
|
package org.keycloak.examples.domainextension.spi.impl;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.examples.domainextension.entities.Company;
|
import org.keycloak.examples.domainextension.jpa.Company;
|
||||||
import org.keycloak.examples.domainextension.services.repository.CompanyRepository;
|
import org.keycloak.examples.domainextension.CompanyRepresentation;
|
||||||
|
import org.keycloak.examples.domainextension.spi.ExampleService;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
public class ExampleServiceImpl implements ExampleService {
|
public class ExampleServiceImpl implements ExampleService {
|
||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private CompanyRepository companyRepository;
|
|
||||||
|
|
||||||
public ExampleServiceImpl(KeycloakSession session) {
|
public ExampleServiceImpl(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
if (getRealm() == null) {
|
if (getRealm() == null) {
|
||||||
throw new IllegalStateException("The service cannot accept a session without a realm in it's context.");
|
throw new IllegalStateException("The service cannot accept a session without a realm in it's context.");
|
||||||
}
|
}
|
||||||
|
|
||||||
companyRepository = new CompanyRepository(getEntityManager());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntityManager getEntityManager() {
|
private EntityManager getEntityManager() {
|
||||||
|
@ -50,18 +50,35 @@ public class ExampleServiceImpl implements ExampleService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Company> listCompanies() {
|
public List<CompanyRepresentation> listCompanies() {
|
||||||
return companyRepository.getAll();
|
List<Company> companyEntities = getEntityManager().createNamedQuery("findByRealm", Company.class)
|
||||||
|
.setParameter("realmId", getRealm().getId())
|
||||||
|
.getResultList();
|
||||||
|
|
||||||
|
List<CompanyRepresentation> result = new LinkedList<>();
|
||||||
|
for (Company entity : companyEntities) {
|
||||||
|
result.add(new CompanyRepresentation(entity));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Company findCompany(String id) {
|
public CompanyRepresentation findCompany(String id) {
|
||||||
return companyRepository.findById(id);
|
Company entity = getEntityManager().find(Company.class, id);
|
||||||
|
return entity==null ? null : new CompanyRepresentation(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCompany(Company company) {
|
public CompanyRepresentation addCompany(CompanyRepresentation company) {
|
||||||
companyRepository.persist(company);
|
Company entity = new Company();
|
||||||
|
String id = company.getId()==null ? KeycloakModelUtils.generateId() : company.getId();
|
||||||
|
entity.setId(id);
|
||||||
|
entity.setName(company.getName());
|
||||||
|
entity.setRealmId(getRealm().getId());
|
||||||
|
getEntityManager().persist(entity);
|
||||||
|
|
||||||
|
company.setId(id);
|
||||||
|
return company;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
|
@ -15,11 +15,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.examples.domainextension.services.spi;
|
package org.keycloak.examples.domainextension.spi.impl;
|
||||||
|
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
import org.keycloak.examples.domainextension.services.ExampleService;
|
import org.keycloak.examples.domainextension.spi.ExampleService;
|
||||||
import org.keycloak.examples.domainextension.services.ExampleServiceImpl;
|
import org.keycloak.examples.domainextension.spi.ExampleServiceProviderFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ public class ExampleServiceProviderFactoryImpl implements ExampleServiceProvider
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "exampleService";
|
return "exampleServiceImpl";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
<addPrimaryKey
|
<addPrimaryKey
|
||||||
constraintName="PK_COMPANY"
|
constraintName="PK_COMPANY"
|
||||||
tableName="DOCDATA_COMPANY"
|
tableName="EXAMPLE_COMPANY"
|
||||||
columnNames="ID"
|
columnNames="ID"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -27,117 +27,7 @@
|
||||||
referencedTableName="REALM"
|
referencedTableName="REALM"
|
||||||
referencedColumnNames="ID"
|
referencedColumnNames="ID"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<createTable tableName="EXAMPLE_REGION">
|
|
||||||
<column name="ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="NAME" type="VARCHAR(255)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="COMPANY_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<addPrimaryKey
|
|
||||||
constraintName="PK_REGION"
|
|
||||||
tableName="EXAMPLE_REGION"
|
|
||||||
columnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<addForeignKeyConstraint
|
|
||||||
constraintName="FK_REGION_COMPANY"
|
|
||||||
baseTableName="EXAMPLE_REGION"
|
|
||||||
baseColumnNames="COMPANY_ID"
|
|
||||||
referencedTableName="EXAMPLE_COMPANY"
|
|
||||||
referencedColumnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<createTable tableName="EXAMPLE_USER_ACCOUNT">
|
|
||||||
<column name="ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USER_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="COMPANY_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<addPrimaryKey
|
|
||||||
constraintName="PK_USER_ACCOUNT"
|
|
||||||
tableName="EXAMPLE_USER_ACCOUNT"
|
|
||||||
columnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<addUniqueConstraint
|
|
||||||
constraintName="UC_USER_ACCOUNT_USER_ID"
|
|
||||||
tableName="EXAMPLE_USER_ACCOUNT"
|
|
||||||
columnNames="USER_ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<addForeignKeyConstraint
|
|
||||||
constraintName="FK_USER_ACCOUNT_USER_ENTITY"
|
|
||||||
baseTableName="EXAMPLE_USER_ACCOUNT"
|
|
||||||
baseColumnNames="USER_ID"
|
|
||||||
referencedTableName="USER_ENTITY"
|
|
||||||
referencedColumnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<addForeignKeyConstraint
|
|
||||||
constraintName="FK_USER_ACCOUNT_COMPANY"
|
|
||||||
baseTableName="EXAMPLE_USER_ACCOUNT"
|
|
||||||
baseColumnNames="COMPANY_ID"
|
|
||||||
referencedTableName="EXAMPLE_COMPANY"
|
|
||||||
referencedColumnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<createTable tableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE">
|
|
||||||
<column name="ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false" />
|
|
||||||
</column>
|
|
||||||
<column name="USER_ACCOUNT_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false" />
|
|
||||||
</column>
|
|
||||||
<column name="REGION_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false" />
|
|
||||||
</column>
|
|
||||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false" />
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<addPrimaryKey
|
|
||||||
constraintName="PK_USER_ACCOUNT_REGION_ROLE"
|
|
||||||
tableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
|
|
||||||
columnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<addForeignKeyConstraint
|
|
||||||
constraintName="FK_USER_ACCOUNT_REGION_ROLE_USER_ACCOUNT"
|
|
||||||
baseTableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
|
|
||||||
baseColumnNames="USER_ACCOUNT_ID"
|
|
||||||
referencedTableName="EXAMPLE_USER_ACCOUNT"
|
|
||||||
referencedColumnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<addForeignKeyConstraint
|
|
||||||
constraintName="FK_USER_ACCOUNT_REGION_ROLE_REGION"
|
|
||||||
baseTableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
|
|
||||||
baseColumnNames="REGION_ID"
|
|
||||||
referencedTableName="EXAMPLE_REGION"
|
|
||||||
referencedColumnNames="ID"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<addForeignKeyConstraint
|
|
||||||
constraintName="FK_USER_ACCOUNT_REGION_ROLE_ROLE"
|
|
||||||
baseTableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
|
|
||||||
baseColumnNames="ROLE_ID"
|
|
||||||
referencedTableName="KEYCLOAK_ROLE"
|
|
||||||
referencedColumnNames="ID"
|
|
||||||
/>
|
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -15,4 +15,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.examples.domainextension.DomainExtensionProviderFactory
|
org.keycloak.examples.domainextension.jpa.ExampleJpaEntityProviderFactory
|
||||||
|
|
|
@ -15,4 +15,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.examples.domainextension.services.spi.ExampleServiceProviderFactoryImpl
|
org.keycloak.examples.domainextension.spi.impl.ExampleServiceProviderFactoryImpl
|
|
@ -15,4 +15,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.examples.domainextension.services.spi.ExampleSpi
|
org.keycloak.examples.domainextension.spi.ExampleSpi
|
||||||
|
|
|
@ -15,4 +15,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.examples.domainextension.rest.ExampleResourceProviderFactory
|
org.keycloak.examples.domainextension.rest.ExampleRealmResourceProviderFactory
|
|
@ -102,6 +102,11 @@
|
||||||
<artifactId>jackson-core</artifactId>
|
<artifactId>jackson-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
|
@ -44,4 +44,10 @@ public interface JpaEntityProvider extends Provider {
|
||||||
*/
|
*/
|
||||||
String getChangelogLocation();
|
String getChangelogLocation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ID of provider factory, which created this provider. Might be used to "compute" the table name of liquibase changelog table.
|
||||||
|
* @return ID of provider factory
|
||||||
|
*/
|
||||||
|
String getFactoryId();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,17 @@ import liquibase.changelog.ChangeSet;
|
||||||
import liquibase.changelog.RanChangeSet;
|
import liquibase.changelog.RanChangeSet;
|
||||||
import liquibase.exception.LiquibaseException;
|
import liquibase.exception.LiquibaseException;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.reflections.Reflections;
|
||||||
|
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||||
|
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -54,25 +59,20 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||||
ThreadLocalSessionContext.setCurrentSession(session);
|
ThreadLocalSessionContext.setCurrentSession(session);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Liquibase liquibase = getLiquibase(connection, defaultSchema);
|
// Run update with keycloak master changelog first
|
||||||
|
Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
|
||||||
|
updateChangeSet(liquibase, liquibase.getChangeLogFile());
|
||||||
|
|
||||||
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
// Run update for each custom JpaEntityProvider
|
||||||
if (!changeSets.isEmpty()) {
|
Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
|
||||||
if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
|
for (JpaEntityProvider jpaProvider : jpaProviders) {
|
||||||
logger.info("Initializing database schema");
|
String customChangelog = jpaProvider.getChangelogLocation();
|
||||||
} else {
|
if (customChangelog != null) {
|
||||||
if (logger.isDebugEnabled()) {
|
String factoryId = jpaProvider.getFactoryId();
|
||||||
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
|
||||||
logger.debugv("Updating database from {0} to {1}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
|
liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
|
||||||
} else {
|
updateChangeSet(liquibase, liquibase.getChangeLogFile());
|
||||||
logger.infov("Updating database");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
liquibase.update((Contexts) null);
|
|
||||||
logger.debug("Completed database update");
|
|
||||||
} else {
|
|
||||||
logger.debug("Database is up to date");
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to update database", e);
|
throw new RuntimeException("Failed to update database", e);
|
||||||
|
@ -81,21 +81,50 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void updateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
|
||||||
|
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
||||||
|
if (!changeSets.isEmpty()) {
|
||||||
|
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
||||||
|
if (ranChangeSets.isEmpty()) {
|
||||||
|
logger.infov("Initializing database schema. Using changelog {0}", changelog);
|
||||||
|
} else {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugv("Updating database from {0} to {1}. Using changelog {2}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog);
|
||||||
|
} else {
|
||||||
|
logger.infov("Updating database. Using changelog {0}", changelog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
liquibase.update((Contexts) null);
|
||||||
|
logger.debugv("Completed database update for changelog {0}", changelog);
|
||||||
|
} else {
|
||||||
|
logger.debugv("Database is up to date for changelog {0}", changelog);
|
||||||
|
|
||||||
|
// Needs to restart liquibase services to clear changeLogHistory.
|
||||||
|
Method resetServices = Reflections.findDeclaredMethod(Liquibase.class, "resetServices");
|
||||||
|
Reflections.invokeMethod(true, resetServices, liquibase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(Connection connection, String defaultSchema) {
|
public void validate(Connection connection, String defaultSchema) {
|
||||||
logger.debug("Validating if database is updated");
|
logger.debug("Validating if database is updated");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Liquibase liquibase = getLiquibase(connection, defaultSchema);
|
// Validate with keycloak master changelog first
|
||||||
|
Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
|
||||||
|
validateChangeSet(liquibase, liquibase.getChangeLogFile());
|
||||||
|
|
||||||
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
// Validate each custom JpaEntityProvider
|
||||||
if (!changeSets.isEmpty()) {
|
Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
|
||||||
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
for (JpaEntityProvider jpaProvider : jpaProviders) {
|
||||||
String errorMessage = String.format("Failed to validate database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update' or use other database",
|
String customChangelog = jpaProvider.getChangelogLocation();
|
||||||
ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
|
if (customChangelog != null) {
|
||||||
throw new RuntimeException(errorMessage);
|
String factoryId = jpaProvider.getFactoryId();
|
||||||
} else {
|
String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
|
||||||
logger.debug("Validation passed. Database is up-to-date");
|
liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
|
||||||
|
validateChangeSet(liquibase, liquibase.getChangeLogFile());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (LiquibaseException e) {
|
} catch (LiquibaseException e) {
|
||||||
|
@ -103,11 +132,28 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
|
protected void validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
|
||||||
|
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
||||||
|
if (!changeSets.isEmpty()) {
|
||||||
|
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
||||||
|
String errorMessage = String.format("Failed to validate database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update' or use other database. Used changelog was %s",
|
||||||
|
ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog);
|
||||||
|
throw new RuntimeException(errorMessage);
|
||||||
|
} else {
|
||||||
|
logger.debugf("Validation passed. Database is up-to-date for changelog %s", changelog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Liquibase getLiquibaseForKeycloakUpdate(Connection connection, String defaultSchema) throws LiquibaseException {
|
||||||
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
||||||
return liquibaseProvider.getLiquibase(connection, defaultSchema);
|
return liquibaseProvider.getLiquibase(connection, defaultSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Liquibase getLiquibaseForCustomProviderUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException {
|
||||||
|
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
||||||
|
return liquibaseProvider.getLiquibaseForCustomUpdate(connection, defaultSchema, changelogLocation, classloader, changelogTableName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,25 +18,18 @@
|
||||||
package org.keycloak.connections.jpa.updater.liquibase.conn;
|
package org.keycloak.connections.jpa.updater.liquibase.conn;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
|
||||||
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
|
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
|
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
|
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
|
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockDatabaseChangeLogGenerator;
|
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockDatabaseChangeLogGenerator;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
|
import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
|
||||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
import liquibase.Liquibase;
|
import liquibase.Liquibase;
|
||||||
import liquibase.changelog.ChangeLogParameters;
|
|
||||||
import liquibase.changelog.ChangeSet;
|
import liquibase.changelog.ChangeSet;
|
||||||
import liquibase.changelog.DatabaseChangeLog;
|
import liquibase.changelog.DatabaseChangeLog;
|
||||||
import liquibase.database.Database;
|
import liquibase.database.Database;
|
||||||
|
@ -46,8 +39,6 @@ import liquibase.database.jvm.JdbcConnection;
|
||||||
import liquibase.exception.LiquibaseException;
|
import liquibase.exception.LiquibaseException;
|
||||||
import liquibase.logging.LogFactory;
|
import liquibase.logging.LogFactory;
|
||||||
import liquibase.logging.LogLevel;
|
import liquibase.logging.LogLevel;
|
||||||
import liquibase.parser.ChangeLogParser;
|
|
||||||
import liquibase.parser.ChangeLogParserFactory;
|
|
||||||
import liquibase.resource.ClassLoaderResourceAccessor;
|
import liquibase.resource.ClassLoaderResourceAccessor;
|
||||||
import liquibase.resource.ResourceAccessor;
|
import liquibase.resource.ResourceAccessor;
|
||||||
import liquibase.servicelocator.ServiceLocator;
|
import liquibase.servicelocator.ServiceLocator;
|
||||||
|
@ -61,12 +52,9 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
|
||||||
private static final Logger logger = Logger.getLogger(DefaultLiquibaseConnectionProvider.class);
|
private static final Logger logger = Logger.getLogger(DefaultLiquibaseConnectionProvider.class);
|
||||||
|
|
||||||
private volatile boolean initialized = false;
|
private volatile boolean initialized = false;
|
||||||
|
|
||||||
private KeycloakSession keycloakSession;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LiquibaseConnectionProvider create(KeycloakSession session) {
|
public LiquibaseConnectionProvider create(KeycloakSession session) {
|
||||||
this.keycloakSession = session;
|
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
|
@ -143,63 +131,26 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
|
||||||
}
|
}
|
||||||
|
|
||||||
String changelog = (database instanceof DB2Database) ? LiquibaseJpaUpdaterProvider.DB2_CHANGELOG : LiquibaseJpaUpdaterProvider.CHANGELOG;
|
String changelog = (database instanceof DB2Database) ? LiquibaseJpaUpdaterProvider.DB2_CHANGELOG : LiquibaseJpaUpdaterProvider.CHANGELOG;
|
||||||
logger.debugf("Using changelog file: %s", changelog);
|
|
||||||
|
|
||||||
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
|
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
|
||||||
DatabaseChangeLog databaseChangeLog = generateDynamicChangeLog(changelog, resourceAccessor, database);
|
|
||||||
|
logger.debugf("Using changelog file %s and changelogTableName %s", changelog, database.getDatabaseChangeLogTableName());
|
||||||
|
|
||||||
return new Liquibase(databaseChangeLog, resourceAccessor, database);
|
return new Liquibase(changelog, resourceAccessor, database);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* We want to be able to provide extra changesets as an extension to the Keycloak data model.
|
public Liquibase getLiquibaseForCustomUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException {
|
||||||
* But we do not want users to be able to not execute certain parts of the Keycloak internal data model.
|
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
|
||||||
* Therefore, we generate a dynamic changelog here that always contains the keycloak changelog file
|
if (defaultSchema != null) {
|
||||||
* and optionally include the user extension changelog files.
|
database.setDefaultSchemaName(defaultSchema);
|
||||||
*
|
}
|
||||||
* @param changelog the changelog file location
|
|
||||||
* @param resourceAccessor the resource accessor
|
|
||||||
* @param database the database
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private DatabaseChangeLog generateDynamicChangeLog(String changelog, ResourceAccessor resourceAccessor, Database database) throws LiquibaseException {
|
|
||||||
ChangeLogParameters changeLogParameters = new ChangeLogParameters(database);
|
|
||||||
ChangeLogParser parser = ChangeLogParserFactory.getInstance().getParser(changelog, resourceAccessor);
|
|
||||||
DatabaseChangeLog keycloakDatabaseChangeLog = parser.parse(changelog, changeLogParameters, resourceAccessor);
|
|
||||||
|
|
||||||
List<String> locations = new ArrayList<>();
|
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(classloader);
|
||||||
Set<JpaEntityProvider> entityProviders = keycloakSession.getAllProviders(JpaEntityProvider.class);
|
database.setDatabaseChangeLogTableName(changelogTableName);
|
||||||
for (JpaEntityProvider entityProvider : entityProviders) {
|
|
||||||
String location = entityProvider.getChangelogLocation();
|
logger.debugf("Using changelog file %s and changelogTableName %s", changelogLocation, database.getDatabaseChangeLogTableName());
|
||||||
if (location != null) {
|
|
||||||
locations.add(location);
|
return new Liquibase(changelogLocation, resourceAccessor, database);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final DatabaseChangeLog dynamicMasterChangeLog;
|
|
||||||
if (locations.isEmpty()) {
|
|
||||||
// If there are no extra changelog locations, we'll just use the keycloak one.
|
|
||||||
dynamicMasterChangeLog = keycloakDatabaseChangeLog;
|
|
||||||
} else {
|
|
||||||
// A change log is essentially not much more than a (big) collection of changesets.
|
|
||||||
// The original (file) destination is not important. So we can just make one big dynamic change log that include all changesets.
|
|
||||||
dynamicMasterChangeLog = new DatabaseChangeLog();
|
|
||||||
dynamicMasterChangeLog.setChangeLogParameters(changeLogParameters);
|
|
||||||
for (ChangeSet changeSet : keycloakDatabaseChangeLog.getChangeSets()) {
|
|
||||||
dynamicMasterChangeLog.addChangeSet(changeSet);
|
|
||||||
}
|
|
||||||
ProxyClassLoader proxyClassLoader = new ProxyClassLoader(JpaUtils.getProvidedEntities(keycloakSession));
|
|
||||||
for (String location : locations) {
|
|
||||||
ResourceAccessor proxyResourceAccessor = new ClassLoaderResourceAccessor(proxyClassLoader);
|
|
||||||
ChangeLogParser locationParser = ChangeLogParserFactory.getInstance().getParser(location, proxyResourceAccessor);
|
|
||||||
DatabaseChangeLog locationDatabaseChangeLog = locationParser.parse(location, changeLogParameters, proxyResourceAccessor);
|
|
||||||
for (ChangeSet changeSet : locationDatabaseChangeLog.getChangeSets()) {
|
|
||||||
dynamicMasterChangeLog.addChangeSet(changeSet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dynamicMasterChangeLog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LogWrapper extends LogFactory {
|
private static class LogWrapper extends LogFactory {
|
||||||
|
|
|
@ -30,4 +30,6 @@ public interface LiquibaseConnectionProvider extends Provider {
|
||||||
|
|
||||||
Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException;
|
Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException;
|
||||||
|
|
||||||
|
Liquibase getLiquibaseForCustomUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,4 +82,16 @@ public class JpaUtils {
|
||||||
return providedEntityClasses;
|
return providedEntityClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of custom table for liquibase updates for give ID of JpaEntityProvider
|
||||||
|
* @param jpaEntityProviderFactoryId
|
||||||
|
* @return table name
|
||||||
|
*/
|
||||||
|
public static String getCustomChangelogTableName(String jpaEntityProviderFactoryId) {
|
||||||
|
String upperCased = jpaEntityProviderFactoryId.toUpperCase();
|
||||||
|
upperCased = upperCased.replaceAll("-", "_");
|
||||||
|
upperCased = upperCased.replaceAll("[^A-Z_]", "");
|
||||||
|
return "DATABASECHANGELOG_" + upperCased.substring(0, Math.min(10, upperCased.length()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.connections.jpa.util;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class JpaUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertTableName() {
|
||||||
|
Assert.assertEquals("DATABASECHANGELOG_FOO", JpaUtils.getCustomChangelogTableName("foo"));
|
||||||
|
Assert.assertEquals("DATABASECHANGELOG_FOOBAR", JpaUtils.getCustomChangelogTableName("foo123bar"));
|
||||||
|
Assert.assertEquals("DATABASECHANGELOG_FOO_BAR", JpaUtils.getCustomChangelogTableName("foo_bar568"));
|
||||||
|
Assert.assertEquals("DATABASECHANGELOG_FOO_BAR_C", JpaUtils.getCustomChangelogTableName("foo-bar-c568"));
|
||||||
|
Assert.assertEquals("DATABASECHANGELOG_EXAMPLE_EN", JpaUtils.getCustomChangelogTableName("example-entity-provider"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -138,6 +138,10 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-adapter-core</artifactId>
|
<artifactId>keycloak-saml-adapter-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authz-client</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
|
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
|
||||||
|
|
Loading…
Reference in a new issue