ActorsRepositoryDefault.java

package org.linkedopenactors.rdfpub.adapter.driven;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.util.ModelBuilder;
import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.query.QueryResults;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.linkedopenactors.rdfpub.domain.ActivityPubObject;
import org.linkedopenactors.rdfpub.domain.Actor;
import org.linkedopenactors.rdfpub.domain.DomainObjectBuilders;
import org.linkedopenactors.rdfpub.domain.IRI;
import org.linkedopenactors.rdfpub.domain.ResourceFactory;
import org.linkedopenactors.rdfpub.domain.service.ActorsRepository;
import org.linkedopenactors.rdfpub.rdf4j.ModelToRdfObject;
import org.springframework.stereotype.Component;

import de.naturzukunft.rdf4j.vocabulary.AS;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 */
@Slf4j
@Component
public class ActorsRepositoryDefault implements ActorsRepository {

	private final Repository repository;
	private final ModelToRdfObject modelToRdfObject;
	private final ResourceFactory resourceFactory;
	private final DomainObjectBuilders domainObjectBuilders;	
	
	/**
	 * @param repository
	 * @param modelToRdfObject
	 * @param resourceFactory
	 * @param domainObjectBuilders
	 */
	public ActorsRepositoryDefault(Repository repository,ModelToRdfObject modelToRdfObject, ResourceFactory resourceFactory, DomainObjectBuilders domainObjectBuilders) {
		this.repository = repository;
		this.modelToRdfObject = modelToRdfObject;
		this.resourceFactory = resourceFactory;
		this.domainObjectBuilders = domainObjectBuilders;
	}
	
	/**
	 * @return {@link List} of all {@link Actor}s of this instance.
	 */
	private List<Actor> findAll() {
    	log.trace("->findAll()");
    	List<Actor> actors = new ArrayList<>();
    	try (RepositoryConnection con = repository.getConnection()) {        
    		Resource[] ctx = new Resource[] {AS.Public};
    		Model all = new ModelBuilder().build();
    		all.addAll(QueryResults.asModel(con.getStatements(null, RDF.TYPE, AS.Application, ctx)));
    		all.addAll(QueryResults.asModel(con.getStatements(null, RDF.TYPE, AS.Group, ctx)));
    		all.addAll(QueryResults.asModel(con.getStatements(null, RDF.TYPE, AS.Organization, ctx)));
    		all.addAll(QueryResults.asModel(con.getStatements(null, RDF.TYPE, AS.Person, ctx)));
    		all.addAll(QueryResults.asModel(con.getStatements(null, RDF.TYPE, AS.Service, ctx)));
    		Set<Resource> actorSubjects = all.stream()
    				.map(stmt->stmt.getSubject())
    				.collect(Collectors.toSet());
    		
    		actors = actorSubjects.stream()
    			.map(as->{    				
    				Model m = QueryResults.asModel(con.getStatements(as, null, null, ctx));
    				ActivityPubObject r = createRdfObject(resourceFactory.iri(as.stringValue()), m);
    				addContexts(r, m);
    				copyNamespaces(r, con);
    				return domainObjectBuilders.actorBuilder().rdfObject(r).build();
    			})
    			.collect(Collectors.toList());
    	}    	
    	log.trace("<-findAll()");
    	return actors;
	}

	private ActivityPubObject createRdfObject(IRI subject, Model resultModel) {
		return modelToRdfObject.toActivityPubObject(Values.iri(subject.toString()), resultModel);
	}

	private void addContexts(ActivityPubObject result, Model resultModel) {
		Optional<ActivityPubObject> resultOpt = Optional.ofNullable(result); 
		Set<IRI> contexts = StreamSupport.stream(resultModel.getStatements(null, null, null).spliterator(), false)
		.map(stmt->stmt.getContext())
		.map(Resource::stringValue)
		.map(resourceFactory::iri)
		.collect(Collectors.toSet());
 		resultOpt.ifPresent(e->e.addContext(contexts));
	}

	private void copyNamespaces(ActivityPubObject rdfObject, RepositoryConnection con) {
		Optional<ActivityPubObject> rdfObjectOpt = Optional.ofNullable(rdfObject);
		List<Namespace> namespaces = new ArrayList<>();
		con.getNamespaces().forEach(ns->namespaces.add(ns));
		rdfObjectOpt.ifPresent(r->{
			namespaces.forEach(nns->r.addNamespace(nns.getPrefix(), resourceFactory.iri(nns.getName())));
		});
	}

	@Override
	public Optional<Actor> findByPreferredUsername(String preferredUsername) {
		Optional<Actor> found = Optional.empty();
		if(Objects.nonNull(preferredUsername)) {
			List<Actor> actors = findAll().stream()
				.filter(a->matchPreferredUsername(a, preferredUsername))
				.collect(Collectors.toList());
			
			if(actors.size()>1) {
				throw new IllegalStateException("preferredUsername '"+preferredUsername+"' is not unique!");
			}
			else if(!actors.isEmpty()) {
				found = Optional.ofNullable(actors.get(0));
			}
		}
		return found;
	}

	@Override
	public Optional<Actor> findByOauth2IssuerUserId(String issuerUserId, URI issuer) {
		String oauth2IssuerUserId = issuerUserId + "@" + issuer.getRawAuthority();
		Optional<Actor> found = Optional.empty();
		
		// TODO optimize, only query for the target user
		List<Actor> actors = findAll().stream()
			.filter(a->matchOauth2IssuerUserId(a, oauth2IssuerUserId))
			.collect(Collectors.toList());
		
		if(actors.size()>1) {
			throw new IllegalStateException("oauth2IssuerUserId '"+oauth2IssuerUserId+"' is not unique!");
		}
		else if(!actors.isEmpty()) {
			found = Optional.ofNullable(actors.get(0));
		}
		return found;
	}

	@Override
	public Optional<Actor> findByIdentifier(String identifier) {
		Optional<Actor> found = Optional.empty();
		if(Objects.nonNull(identifier)) {
			List<Actor> actors = findAll().stream()
					.filter(a->identifier.equals(a.getIdentifier()))
//					.filter(a->matchIdentifier(a, identifier))
					.collect(Collectors.toList());
			if(actors.size()>1) {
				throw new IllegalStateException("identifier '"+identifier+"' is not unique!");
			}
			else if(!actors.isEmpty()) {
				found = Optional.ofNullable(actors.get(0));
			}
		}
		return found;
	}

	private boolean matchPreferredUsername(Actor actor, String preferredUsername) {
		return actor.getPreferredUsername()
				.map(p->p.equals(preferredUsername))
				.orElse(false);	
	}

	private boolean matchOauth2IssuerUserId(Actor actor, String oauth2IssuerUserId) {
		return actor.getOuth2IssuerUserId()
				.map(p->p.equals(oauth2IssuerUserId))
				.orElse(false);	
	}
}