Skip to content

Commit

Permalink
[Enhancement #1] Allow specifying multiple repository ids to replicat…
Browse files Browse the repository at this point in the history
…e user metadata to.
  • Loading branch information
ledsoft committed Apr 11, 2024
1 parent e0e25b5 commit 910afd4
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 79 deletions.
13 changes: 8 additions & 5 deletions src/main/java/cz/cvut/kbss/keycloak/provider/Configuration.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package cz.cvut.kbss.keycloak.provider;

import cz.cvut.kbss.keycloak.provider.model.UserAccount;

import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -14,7 +17,7 @@ public class Configuration {

private final String realmId;

private final String repositoryId;
private final List<String> repositoryIds;

private final String repositoryUsername;

Expand All @@ -41,14 +44,14 @@ public class Configuration {
(Map<String, Object>) parseComponents(components).get("al-db-server");
final GraphDbUrlParser parser = new GraphDbUrlParser(dbServer.get("url").toString());
this.dbServerUrl = parser.getGraphdbUrl();
this.repositoryId = parser.getRepositoryId();
this.repositoryIds = parser.getRepositoryIds();
final Map<String, Object> authServer =
(Map<String, Object>) parseComponents(components).get("al-auth-server");
final AuthServerParser aParser = new AuthServerParser(authServer.get("url").toString());
this.realmId = aParser.getRealmId();
} else {
this.dbServerUrl = getProperty("DB_SERVER_URL");
this.repositoryId = getProperty("DB_SERVER_REPOSITORY_ID");
this.repositoryIds = Arrays.asList(getProperty("DB_SERVER_REPOSITORY_ID").split(","));
this.realmId = getProperty("REALM_ID");
}
this.repositoryUsername = getProperty("REPOSITORY_USERNAME");
Expand Down Expand Up @@ -96,8 +99,8 @@ public String getRealmId() {
return realmId;
}

public String getRepositoryId() {
return repositoryId;
public List<String> getRepositoryIds() {
return repositoryIds;
}

public String getRepositoryUsername() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,5 @@ private UserAccount resolveUser(AdminEvent event) {

@Override
public void close() {
userAccountDao.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import cz.cvut.kbss.keycloak.provider.dao.GraphDBUserDao;
import cz.cvut.kbss.keycloak.provider.dao.UserAccountDao;
import org.eclipse.rdf4j.repository.Repository;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
Expand All @@ -17,18 +16,18 @@ public class DataReplicationProviderFactory implements EventListenerProviderFact

private Configuration configuration;

private Repository repository;
private Repositories repositories;

@Override
public EventListenerProvider create(KeycloakSession keycloakSession) {
LOG.info("Creating EventListenerProvider.");
if (repository == null) {
// Init persistence factory lazily, because GraphDB won't start until its OIDC provider (Keycloak) is available
this.repository = PersistenceFactory.connect(configuration);
if (repositories == null) {
// Init persistence factory lazily to ensure the target db server is up and running
this.repositories = PersistenceFactory.connect(configuration);
}
return new DataReplicationProvider(
new KeycloakAdapter(keycloakSession.users(), keycloakSession.realms(), configuration),
new UserAccountDao(repository.getConnection(), configuration.getVocabulary(),
new UserAccountDao(repositories, configuration.getVocabulary(),
configuration.getRepositoryLanguage()),
new GraphDBUserDao(configuration));
}
Expand All @@ -45,8 +44,8 @@ public void postInit(KeycloakSessionFactory keycloakSessionFactory) {

@Override
public void close() {
if (repository != null) {
repository.shutDown();
if (repositories != null) {
repositories.close();
}
}

Expand Down
27 changes: 16 additions & 11 deletions src/main/java/cz/cvut/kbss/keycloak/provider/GraphDbUrlParser.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cz.cvut.kbss.keycloak.provider;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -9,24 +11,27 @@ public class GraphDbUrlParser {

private String graphdbUrl;

private String repositoryId;
private final List<String> repositoryIds = new ArrayList<>();

public GraphDbUrlParser(String sparqlEndpoint) {
final Matcher m = regex.matcher(sparqlEndpoint);
if (!m.matches()) {
throw new RuntimeException(MessageFormat.format(
"The URL {0} is not a graphDb SPARQL endpoint URL (conforming to the pattern {1})",
sparqlEndpoint, regex.pattern()));
public GraphDbUrlParser(String sparqlEndpoints) {
final String[] endpoints = sparqlEndpoints.split(",");
for (String s : endpoints) {
final Matcher m = regex.matcher(s);
if (!m.matches()) {
throw new RuntimeException(MessageFormat.format(
"The URL {0} is not a graphDb SPARQL endpoint URL (conforming to the pattern {1})",
s, regex.pattern()));
}
this.graphdbUrl = m.group(1);
repositoryIds.add(m.group(2));
}
graphdbUrl = m.group(1);
repositoryId = m.group(2);
}

public String getGraphdbUrl() {
return graphdbUrl;
}

public String getRepositoryId() {
return repositoryId;
public List<String> getRepositoryIds() {
return repositoryIds;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package cz.cvut.kbss.keycloak.provider;

import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.manager.RemoteRepositoryManager;
import org.eclipse.rdf4j.repository.manager.RepositoryManager;
import org.eclipse.rdf4j.repository.manager.RepositoryProvider;
Expand All @@ -11,20 +10,25 @@ class PersistenceFactory {

private static final Logger LOG = LoggerFactory.getLogger(PersistenceFactory.class);

static Repository connect(Configuration configuration) {
LOG.info("Initializing connection to repository {}.", configuration.getRepositoryId());
final String url = configuration.getDbServerUrl() + "/repositories/" + configuration.getRepositoryId();
return connectToRemoteRepository(url, configuration);
static Repositories connect(Configuration configuration) {
final Repositories repositories = new Repositories();
final RepositoryManager repositoryManager = connectToRepositoryServer(configuration);
configuration.getRepositoryIds().forEach(repoId -> {
LOG.info("Connecting to repository {}.", repoId);
repositories.add(repositoryManager.getRepository(repoId));
});
return repositories;
}

private static Repository connectToRemoteRepository(String repoUri, Configuration configuration) {
final RepositoryManager manager = RepositoryProvider.getRepositoryManagerOfRepository(repoUri);
private static RepositoryManager connectToRepositoryServer(Configuration configuration) {
LOG.info("Initializing connection to repository server {}.", configuration.getDbServerUrl());
final RepositoryManager manager = RepositoryProvider.getRepositoryManager(configuration.getDbServerUrl());
final RemoteRepositoryManager remoteManager = (RemoteRepositoryManager) manager;
final String username = configuration.getRepositoryUsername();
if (username != null) {
final String password = configuration.getRepositoryPassword();
remoteManager.setUsernameAndPassword(username, password);
}
return manager.getRepository(configuration.getRepositoryId());
return manager;
}
}
37 changes: 37 additions & 0 deletions src/main/java/cz/cvut/kbss/keycloak/provider/Repositories.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cz.cvut.kbss.keycloak.provider;

import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class Repositories {

private final List<Repository> repositories = new ArrayList<>();

public void add(Repository repository) {
repositories.add(repository);
}

public void execute(Consumer<RepositoryConnection> executor) {
repositories.forEach(r -> {
try (final RepositoryConnection conn = r.getConnection()) {
conn.begin();
executor.accept(conn);
conn.commit();
}
});
}

public void close() {
repositories.forEach(Repository::shutDown);
}

public ValueFactory getValueFactory() {
assert !repositories.isEmpty();
return repositories.get(0).getValueFactory();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public GraphDBUserDao(Configuration configuration) {

public void addUser(UserAccount userAccount) {
final GraphDBUserDto userDto = new GraphDBUserDto();
userDto.addAccessToRepository(configuration.getRepositoryId());
userDto.addAccessToRepository(configuration.getRepositoryIds());
assert userAccount.getUsername() != null;
postUserToGraphDB(userAccount.getUsername(), userDto);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cz.cvut.kbss.keycloak.provider.dao;

import cz.cvut.kbss.keycloak.provider.Repositories;
import cz.cvut.kbss.keycloak.provider.model.UserAccount;
import cz.cvut.kbss.keycloak.provider.model.Vocabulary;
import org.eclipse.rdf4j.model.IRI;
Expand All @@ -20,27 +21,25 @@ public class UserAccountDao {

private final ValueFactory vf;

private final RepositoryConnection connection;
private final Repositories repositories;

private final Vocabulary vocabulary;

private final String repoLang;

public UserAccountDao(RepositoryConnection connection, Vocabulary vocabulary, String repoLang) {
this.connection = connection;
this.vf = connection.getValueFactory();
public UserAccountDao(Repositories repositories, Vocabulary vocabulary, String repoLang) {
this.repositories = repositories;
this.vf = repositories.getValueFactory();
this.vocabulary = vocabulary;
this.repoLang = repoLang;
}

public void persist(UserAccount userAccount) {
Objects.requireNonNull(userAccount);
connection.begin();
persistInTransaction(userAccount);
connection.commit();
repositories.execute(conn -> persistInTransaction(userAccount, conn));
}

private void persistInTransaction(UserAccount userAccount) {
private void persistInTransaction(UserAccount userAccount, RepositoryConnection connection) {
if (Objects.isNull(UserAccount.getContext()) || UserAccount.getContext().isEmpty()) {
generateUserMetadataStatements(userAccount).forEach(connection::add);
} else {
Expand Down Expand Up @@ -86,19 +85,15 @@ private Literal stringLiteral(String value) {

public void update(UserAccount userAccount) {
Objects.requireNonNull(userAccount);
connection.begin();
final IRI subject = vf.createIRI(userAccount.getUri().toString());
connection.remove(subject, vf.createIRI(vocabulary.getFirstName()), null);
connection.remove(subject, vf.createIRI(vocabulary.getLastName()), null);
connection.remove(subject, vf.createIRI(vocabulary.getUsername()), null);
if (vocabulary.getEmail() != null) {
connection.remove(subject, vf.createIRI(vocabulary.getEmail()), null);
}
persistInTransaction(userAccount);
connection.commit();
}

public void close() {
connection.close();
repositories.execute(conn -> {
final IRI subject = vf.createIRI(userAccount.getUri().toString());
conn.remove(subject, vf.createIRI(vocabulary.getFirstName()), null);
conn.remove(subject, vf.createIRI(vocabulary.getLastName()), null);
conn.remove(subject, vf.createIRI(vocabulary.getUsername()), null);
if (vocabulary.getEmail() != null) {
conn.remove(subject, vf.createIRI(vocabulary.getEmail()), null);
}
persistInTransaction(userAccount, conn);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;

public class GraphDBUserDto {
Expand All @@ -28,10 +29,12 @@ public Collection<String> getGrantedAuthorities() {
return grantedAuthorities;
}

public void addAccessToRepository(String repositoryId) {
Objects.requireNonNull(repositoryId);
grantedAuthorities.add(WRITE_REPO + repositoryId);
grantedAuthorities.add(READ_REPO + repositoryId);
public void addAccessToRepository(List<String> repositoryIds) {
Objects.requireNonNull(repositoryIds);
repositoryIds.forEach(rId -> {
grantedAuthorities.add(WRITE_REPO + rId);
grantedAuthorities.add(READ_REPO + rId);
});
}

static class AppSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@

import org.junit.jupiter.api.Test;

import java.util.Collections;

public class GraphDbUrlParserTest {

@Test
public void parsesGraphDbRepositoryUrlCorrectly() {
final String sparqlEndpointUrl = "https://localhost/služby/graphdb/repositories/kodi";
final GraphDbUrlParser parser = new GraphDbUrlParser(sparqlEndpointUrl);
assertEquals("kodi",parser.getRepositoryId());
assertEquals("https://localhost/služby/graphdb",parser.getGraphdbUrl());
assertEquals(Collections.singletonList("kodi"), parser.getRepositoryIds());
assertEquals("https://localhost/služby/graphdb", parser.getGraphdbUrl());
}

@Test
Expand Down
Loading

0 comments on commit 910afd4

Please sign in to comment.