CollectionRepositoryDefault.java

package org.linkedopenactors.rdfpub.adapter.driven;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.query.Binding;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryResults;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.linkedopenactors.rdfpub.domain.AS;
import org.linkedopenactors.rdfpub.domain.Activity;
import org.linkedopenactors.rdfpub.domain.ActivityBuilder;
import org.linkedopenactors.rdfpub.domain.Actor;
import org.linkedopenactors.rdfpub.domain.CollectionRepository;
import org.linkedopenactors.rdfpub.domain.DomainObjectBuilders;
import org.linkedopenactors.rdfpub.domain.OrderedCollectionPage;
import org.linkedopenactors.rdfpub.domain.OrderedCollectionPageWithActivities;
import org.linkedopenactors.rdfpub.domain.ResourceFactory;

import de.naturzukunft.rdf4j.utils.ModelLogger;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CollectionRepositoryDefault implements CollectionRepository {
	
	private final RdfTypeRepository rdfTypeRepository;
	private final Repository repository;
	private final ResourceFactory resourceFactory;
	private Actor actor;
	private final DomainObjectBuilders domainObjectBuilders;

	public CollectionRepositoryDefault(Actor actor, Repository repository, 
			ResourceFactory resourceFactory, RdfTypeRepository rdfTypeRepository, DomainObjectBuilders domainObjectBuilders)  {
		this.actor = actor;
		this.repository = repository;
		this.resourceFactory = resourceFactory;
		this.rdfTypeRepository = rdfTypeRepository;
		this.domainObjectBuilders = domainObjectBuilders;
	}
	
	@Override
	public OrderedCollectionPageWithActivities getInbox(Integer startIndex, Integer pageSize) {
		return getCollection(Values.iri(actor.getInboxEndpoint().toString()), pageSize, startIndex);
	}

	@Override
	public OrderedCollectionPageWithActivities getPublic(Integer startIndex, Integer pageSize) {
		return getCollection(Values.iri(AS.Public().toString()), pageSize, startIndex);
	}

	@Override
	public OrderedCollectionPageWithActivities getOutbox(Integer startIndex, Integer pageSize) {
		return getCollection(Values.iri(actor.getOutboxEndpoint().toString()), pageSize, startIndex);
	}

	private OrderedCollectionPageWithActivities getCollection(IRI namedGraphOfCollectionToUse, Integer pageSize, Integer startIndex) {

		List<Activity> activities  = getActivities(namedGraphOfCollectionToUse, pageSize, startIndex);

		String subjectOfOrderedCollection = String.format("%s?pageSize=%s&startIndex=%s", namedGraphOfCollectionToUse.stringValue(), pageSize, startIndex);
		
		OrderedCollectionPage page = domainObjectBuilders.orderedCollectionPageBuilder()
			.subject(resourceFactory.iri(subjectOfOrderedCollection))
			.summary("collection: " +namedGraphOfCollectionToUse.stringValue() + "; pageSize: " + pageSize + "; startIndex: " + startIndex)
			.partOf(resourceFactory.iri(namedGraphOfCollectionToUse.stringValue()))
			.totalItems(activities.size())
			.items( activities.stream().map(Activity::getSubject).collect(Collectors.toSet()) )
			.build();
		
		OrderedCollectionPageWithActivities pageWithActivities = new OrderedCollectionPageWithActivities(activities, page);
		
		return pageWithActivities;
	}

	private List<Activity> getActivities(IRI namedGraphToUse, int pageSize, int startIndex) {
		String select = ("PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "SELECT DISTINCT ?entity ?published ?type ?actor ?name ?summary ?object\n");
				
		if(namedGraphToUse!=null) {
			select = select + String.format("FROM <%s> \n", namedGraphToUse.stringValue());  
		}

		String typeDefFroms = "FROM " + rdfTypeRepository.getContextsOfAddedTypeDefinitions().stream()
				.map(IRI::stringValue)
				.map(iri->"<"+iri+">")
				.collect(Collectors.joining("\nFROM "));
			
		String queryString = String.format(select 
				+ typeDefFroms
				+ "\nWHERE\n"
				+ "{\n"
				+ "  ?entity rdf:type ?type.\n"
				+ "  OPTIONAL {?entity <https://www.w3.org/ns/activitystreams#published> ?published .}\n"
				+ "  OPTIONAL {?entity <https://www.w3.org/ns/activitystreams#actor> ?actor .}\n"
				+ "  OPTIONAL {?entity <https://www.w3.org/ns/activitystreams#name> ?name .}\n"
				+ "  OPTIONAL {?entity <https://www.w3.org/ns/activitystreams#summary> ?summary .}\n"
				+ "  OPTIONAL {?entity <https://www.w3.org/ns/activitystreams#object> ?object .}\n"
				+ "  ?type rdfs:subClassOf* <https://www.w3.org/ns/activitystreams#Activity> .\n"
				+ "}\n"
				+ "ORDER BY desc(?published)\n"
				+ "LIMIT %s\n"
				+ "OFFSET %s", pageSize, startIndex)
				;
		
		log.trace("queryString: \n" + queryString);
		
		try (RepositoryConnection conn = repository.getConnection()) {
			TupleQuery tupleQuery = conn.prepareTupleQuery(queryString);
			TupleQueryResult result = tupleQuery.evaluate();
			log.trace(namedGraphToUse + " -> result.hasNext(): " + result.hasNext());
			
			Model pureStatements = QueryResults.asModel(conn.getStatements(null, null, null, namedGraphToUse));
			ModelLogger.trace(log, pureStatements, "pureStatements of '"+namedGraphToUse+"'");
			
			List<Activity> activities = new ArrayList<>();
			while (result.hasNext()) {
				BindingSet bindingSet = result.next();
				log.trace("bindingSet: " + bindingSet);
				
				String subject = bindingSet.getBinding("entity").getValue().stringValue();
				org.linkedopenactors.rdfpub.domain.IRI type = toIRI(bindingSet, "type").orElseThrow(()->new IllegalStateException("type is mandatory!"));
				ActivityBuilder activityBuilder = domainObjectBuilders.activityBuilder();				
				activityBuilder = activityBuilder.subject(getActivityType(type), resourceFactory.iri(subject));
				toString(bindingSet, "published").ifPresent(activityBuilder::published);
				toString(bindingSet, "name").ifPresent(activityBuilder::name);
				toString(bindingSet, "summary").ifPresent(activityBuilder::summary);
				toIRI(bindingSet, "actor").ifPresent(activityBuilder::actor);
				toIRI(bindingSet, "object").ifPresent(activityBuilder::object);
				activities.add(activityBuilder.build());
			}
			return activities;			
		}
	}
	
	private org.linkedopenactors.rdfpub.domain.ActivityType getActivityType(org.linkedopenactors.rdfpub.domain.IRI type) {
		if(org.linkedopenactors.rdfpub.domain.AS.Create().equals(type)) {
			return org.linkedopenactors.rdfpub.domain.ActivityType.CREATE;			
		} else if(org.linkedopenactors.rdfpub.domain.AS.Create().equals(type)) {
			return org.linkedopenactors.rdfpub.domain.ActivityType.UPDATE;			
		} else {
			throw new IllegalStateException(" unsupported activity type: " + type);
		}
	}
	
	private Optional<org.linkedopenactors.rdfpub.domain.IRI> toIRI(BindingSet bindingSet, String bindingName) {		
				return toString(bindingSet, bindingName)
				.map(resourceFactory::iri);
	}

	private Optional<String> toString(BindingSet bindingSet, String bindingName) {		
		return Optional.ofNullable(
				bindingSet.getBinding(bindingName))
				.map(Binding::getValue)
				.map(Value::stringValue);
	}
}