Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement#1 multiple target repositories #2

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 55 additions & 41 deletions README.md

Large diffs are not rendered by default.

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
Loading