uk.ac.open.kmi.iserve.sal.manager.impl.ServiceManagerIndexRdf.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.open.kmi.iserve.sal.manager.impl.ServiceManagerIndexRdf.java

Source

/*
 * Copyright (c) 2013. Knowledge Media Institute - The Open University
 *
 * 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 uk.ac.open.kmi.iserve.sal.manager.impl;

import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.open.kmi.iserve.core.ConfigurationProperty;
import uk.ac.open.kmi.iserve.core.iServeProperty;
import uk.ac.open.kmi.iserve.sal.exception.SalException;
import uk.ac.open.kmi.iserve.sal.exception.ServiceException;
import uk.ac.open.kmi.iserve.sal.manager.ServiceManager;
import uk.ac.open.kmi.msm4j.*;

import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Singleton
public class ServiceManagerIndexRdf extends ServiceManagerSparql implements ServiceManager {

    private static final Logger log = LoggerFactory.getLogger(ServiceManagerIndexRdf.class);

    // Service -> Operations
    private ConcurrentMap<URI, Set<URI>> svcOpMap;
    // Operation -> Inputs
    private ConcurrentMap<URI, Set<URI>> opInputMap;
    // Inputs - > Mandatory Parts
    private ConcurrentMap<URI, Set<URI>> messageMandatoryPartsMap;
    // Inputs - > Optional Parts
    private ConcurrentMap<URI, Set<URI>> messageOptionalPartsMap;
    // Operation -> Output
    private ConcurrentMap<URI, Set<URI>> opOutputMap;
    // Element -> Model Refs
    private ConcurrentMap<URI, Set<URI>> modelReferencesMap;

    @Inject
    ServiceManagerIndexRdf(EventBus eventBus, SparqlGraphStoreFactory graphStoreFactory,
            @iServeProperty(ConfigurationProperty.ISERVE_URL) String iServeUri,
            @iServeProperty(ConfigurationProperty.SERVICES_SPARQL_QUERY) String sparqlQueryEndpoint,
            @iServeProperty(ConfigurationProperty.SERVICES_SPARQL_UPDATE) String sparqlUpdateEndpoint,
            @iServeProperty(ConfigurationProperty.SERVICES_SPARQL_SERVICE) String sparqlServiceEndpoint)
            throws SalException {

        super(eventBus, graphStoreFactory, iServeUri, sparqlQueryEndpoint, sparqlUpdateEndpoint,
                sparqlServiceEndpoint);

        log.debug(
                "Creating Service Manager. iServe URI - {}\n SPARQL Query - {}\n, SPARQL Update - {}\n, SPARQL Service - {}",
                iServeUri, sparqlQueryEndpoint, sparqlUpdateEndpoint, sparqlServiceEndpoint);

        this.svcOpMap = new ConcurrentHashMap<URI, Set<URI>>();
        this.opInputMap = new ConcurrentHashMap<URI, Set<URI>>();
        this.messageMandatoryPartsMap = new ConcurrentHashMap<URI, Set<URI>>();
        this.messageOptionalPartsMap = new ConcurrentHashMap<URI, Set<URI>>();
        this.opOutputMap = new ConcurrentHashMap<URI, Set<URI>>();
        this.modelReferencesMap = new ConcurrentHashMap<URI, Set<URI>>();

        initialise();
    }

    /**
     * Obtains a list of service URIs with all the services known to the system
     *
     * @return list of URIs with all the services in the registry
     */
    @Override
    public Set<URI> listServices() {
        return this.svcOpMap.keySet();
    }

    /**
     * Obtains the list of operation URIs for a given Operation
     *
     * @param serviceUri the service URI
     * @return a List of URIs with the operations provided by the service. If there are no operations, the List should be empty NOT null.
     */
    @Override
    public Set<URI> listOperations(URI serviceUri) {
        if (serviceUri == null) {
            return ImmutableSet.of();
        }

        ImmutableSet.Builder<URI> result = ImmutableSet.builder();
        for (URI operation : this.svcOpMap.get(serviceUri)) {
            result.add(operation);
        }

        return result.build();
    }

    /**
     * Obtains the list of input URIs for a given Operation
     *
     * @param operationUri the operation URI
     * @return a List of URIs with the inputs of the operation. If no input is necessary the List should be empty NOT null.
     */
    @Override
    public Set<URI> listInputs(URI operationUri) {
        if (operationUri == null) {
            return ImmutableSet.of();
        }

        return ImmutableSet.copyOf(this.opInputMap.get(operationUri));
    }

    /**
     * Given an operation, this method obtains the list of input URIs mapped to their annotated types (i.e., modelReferences).
     * Note that the same input may have several annotations indicating the type.
     *
     * @param operationUri the URI for which we want to obtain the inputs and their annotated types
     * @return a Multimap with the inputs and their corresponding types.
     */
    @Override
    public Multimap<URI, URI> listTypedInputs(URI operationUri) {
        if (operationUri == null) {
            return ImmutableMultimap.of();
        }

        ImmutableMultimap.Builder<URI, URI> result = ImmutableMultimap.builder();
        for (URI input : this.opInputMap.get(operationUri)) {
            result.putAll(input, this.modelReferencesMap.get(input));
        }

        return result.build();
    }

    /**
     * Obtains the list of output URIs for a given Operation
     *
     * @param operationUri the operation URI
     * @return a List of URIs with the outputs of the operation. If no output is provided the List should be empty NOT null.
     */
    @Override
    public Set<URI> listOutputs(URI operationUri) {
        if (operationUri == null) {
            return ImmutableSet.of();
        }

        return ImmutableSet.copyOf(this.opOutputMap.get(operationUri));
    }

    /**
     * Given an operation, this method obtains the list of output URIs mapped to their annotated types (i.e., modelReferences).
     * Note that the same input may have several annotations indicating the type.
     *
     * @param operationUri the URI for which we want to obtain the outputs and their annotated types
     * @return a Multimap with the outputs and their corresponding types.
     */
    @Override
    public Multimap<URI, URI> listTypedOutputs(URI operationUri) {
        if (operationUri == null) {
            return ImmutableMultimap.of();
        }

        ImmutableMultimap.Builder<URI, URI> result = ImmutableMultimap.builder();
        for (URI output : this.opOutputMap.get(operationUri)) {
            result.putAll(output, this.modelReferencesMap.get(output));
        }

        return result.build();
    }

    /**
     * Obtains the list of mandatory parts for a given Message Content
     *
     * @param messageContent the message content URI
     * @return a List of URIs with the mandatory parts of the message content. If there are no parts the List should be empty NOT null.
     */
    @Override
    public Set<URI> listMandatoryParts(URI messageContent) {
        if (messageContent == null) {
            return ImmutableSet.of();
        }

        return ImmutableSet.copyOf(this.messageMandatoryPartsMap.get(messageContent));
    }

    /**
     * Obtains the list of optional parts for a given Message Content
     *
     * @param messageContent the message content URI
     * @return a Set of URIs with the optional parts of the message content. If there are no parts the Set should be empty NOT null.
     */
    @Override
    public Set<URI> listOptionalParts(URI messageContent) {
        if (messageContent == null) {
            return ImmutableSet.of();
        }

        return ImmutableSet.copyOf(this.messageOptionalPartsMap.get(messageContent));
    }

    /**
     * Creates a Service Description in the system.
     * Only needs to be fed with an MSM Service description.
     *
     * After successfully adding a service, implementations of this method should raise a {@code ServiceCreatedEvent}
     *
     * @param service the input service description in terms of MSM
     * @return the URI this service description was saved to
     * @throws ServiceException
     */
    @Override
    public URI addService(Service service) throws ServiceException {

        URI serviceUri = super.addService(service);
        if (serviceUri != null) {
            // Index service
            this.indexService(service);
        }

        return service.getUri();
    }

    /**
     * Deletes the given service
     *
     * After successfully deleting a service, implementations of this method should raise a {@code ServiceDeletedEvent}
     *
     * @param serviceUri the URI of the service to delete
     * @return True if it was deleted or false otherwise
     * @throws ServiceException
     */
    @Override
    public boolean deleteService(URI serviceUri) throws ServiceException {

        if (super.deleteService(serviceUri)) {
            // Update cache
            this.removeServiceFromCache(serviceUri);
            return true;
        }

        return false;
    }

    /**
     * Deletes the given service
     *
     * After successfully deleting a service, implementations of this method should raise a {@code ServiceDeletedEvent}
     *
     * @param service the service to delete
     * @return True if it was deleted or false otherwise
     * @throws ServiceException
     */
    @Override
    public boolean deleteService(Service service) throws ServiceException {
        if (super.deleteService(service)) {
            // Update cache
            this.removeServiceFromCache(service.getUri());
            return true;
        }

        return false;
    }

    /**
     * Deletes all the services on the registry.
     * This operation cannot be undone. Use with care.
     *
     * After successfully clearing the services, implementations of this method should raise a {@code ServicesClearedEvent}
     *
     * @return true if the service registry was cleared.
     * @throws ServiceException
     */
    @Override
    public boolean clearServices() throws ServiceException {

        if (super.clearServices()) {
            // Initialise
            initialise();
            return true;
        }

        return true;

    }

    /**
     * Determines whether a service is known to the registry
     *
     * @param serviceUri the URI of the service being looked up
     * @return True if it is registered in the server
     * @throws ServiceException
     */
    @Override
    public boolean serviceExists(URI serviceUri) throws ServiceException {
        return this.svcOpMap.containsKey(serviceUri);
    }

    /**
     * Obtains the list of model references for a given element.
     *
     * @param elementUri the URI of the element for which we want to obtain the model references
     * @return a List of URIs with the model references of the given element. If there are no model references the List should be empty NOT null.
     */
    @Override
    public Set<URI> listModelReferences(URI elementUri) {
        if (elementUri == null) {
            return ImmutableSet.of();
        }

        return ImmutableSet.copyOf(this.modelReferencesMap.get(elementUri));
    }

    /**
     * This method will be called when the server is initialised.
     * If necessary it should take care of updating any indexes on boot time.
     */
    private void initialise() {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.start();
        populateCache();
        stopwatch.stop();
        log.info("Cache populated. Time taken {}", stopwatch);
    }

    private void populateCache() {
        // clear
        this.svcOpMap.clear();
        this.opInputMap.clear();
        this.messageMandatoryPartsMap.clear();
        this.messageOptionalPartsMap.clear();
        this.opOutputMap.clear();
        this.modelReferencesMap.clear();

        // fill up
        Set<URI> services = super.listServices();
        for (URI svc : services) {
            Service service = null;
            try {
                service = this.getService(svc);
                indexService(service);
            } catch (ServiceException e) {
                log.error("Error populating cache. Cannot retrieve service.", e);
            }
        }
    }

    private void indexService(Service service) {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.start();
        List<Operation> operations = service.getOperations();
        Set<URI> svcOps = new HashSet<URI>();
        for (Operation operation : operations) {
            svcOps.add(operation.getUri());
            indexOperation(operation);
        }
        // Set the svcOp map
        this.svcOpMap.put(service.getUri(), svcOps);
        // Index the modelReferences
        indexModelReferences(service);
        stopwatch.stop();
        log.info("Service - {} - indexed. Time taken {}", service.getUri(), stopwatch);
    }

    private void indexModelReferences(AnnotableResource annotableResource) {
        List<uk.ac.open.kmi.msm4j.Resource> modelRefs = annotableResource.getModelReferences();
        Set<URI> modelRefsUris = new HashSet<URI>();
        for (uk.ac.open.kmi.msm4j.Resource modelRef : modelRefs) {
            modelRefsUris.add(modelRef.getUri());
        }
        this.modelReferencesMap.put(annotableResource.getUri(), modelRefsUris);
    }

    private void indexOperation(Operation operation) {

        // Note this method assumes only one input per operation
        // If there are more only the last will be mapped
        Set<URI> partsUris;
        Set<URI> messagesUris;

        messagesUris = new HashSet<URI>();
        List<MessageContent> inputs = operation.getInputs();
        for (MessageContent input : inputs) {
            messagesUris.add(input.getUri());
            indexModelReferences(input);
            indexMandatoryParts(input);
            indexOptionalParts(input);
        }
        // Index inputs
        this.opInputMap.put(operation.getUri(), messagesUris);

        // Process Output
        messagesUris = new HashSet<URI>();
        List<MessageContent> outputs = operation.getInputs();
        for (MessageContent output : outputs) {
            messagesUris.add(output.getUri());
            indexModelReferences(output);
            indexMandatoryParts(output);
            indexOptionalParts(output);
        }
        // Index outputs
        this.opOutputMap.put(operation.getUri(), messagesUris);

        // Add model refs
        indexModelReferences(operation);
    }

    private void indexOptionalParts(MessageContent messageContent) {
        Set<URI> partsUris;// Index optional parts
        partsUris = new HashSet<URI>();
        List<MessagePart> optionalParts = messageContent.getOptionalParts();
        for (MessagePart optionalPart : optionalParts) {
            partsUris.add(optionalPart.getUri());
            indexModelReferences(optionalPart);
        }
        this.messageOptionalPartsMap.put(messageContent.getUri(), partsUris);
    }

    private void indexMandatoryParts(MessageContent messageContent) {
        Set<URI> partsUris;// Index mandatory parts
        partsUris = new HashSet<URI>();
        List<MessagePart> mandatoryParts = messageContent.getMandatoryParts();
        for (MessagePart mandatoryPart : mandatoryParts) {
            partsUris.add(mandatoryPart.getUri());
            indexModelReferences(mandatoryPart);
        }
        this.messageMandatoryPartsMap.put(messageContent.getUri(), partsUris);
    }

    private void removeServiceFromCache(URI serviceUri) {

        Set<URI> operations = this.svcOpMap.remove(serviceUri);
        for (URI operation : operations) {
            removeOperationFromCache(operation);
        }
        this.modelReferencesMap.remove(serviceUri);
    }

    private void removeOperationFromCache(URI operation) {

        // Clean input
        Set<URI> parts;
        Set<URI> inputUris = this.opInputMap.remove(operation);
        for (URI inputUri : inputUris) {
            this.modelReferencesMap.remove(inputUri);
            parts = this.messageMandatoryPartsMap.remove(inputUri);
            for (URI part : parts) {
                this.modelReferencesMap.remove(part);
            }
            parts = this.messageOptionalPartsMap.remove(inputUri);
            for (URI part : parts) {
                this.modelReferencesMap.remove(part);
            }
        }

        // Clean output
        Set<URI> outputUris = this.opOutputMap.remove(operation);
        for (URI outputUri : outputUris) {
            this.modelReferencesMap.remove(outputUri);
            parts = this.messageMandatoryPartsMap.remove(outputUri);
            for (URI part : parts) {
                this.modelReferencesMap.remove(part);
            }
            parts = this.messageOptionalPartsMap.remove(outputUri);
            for (URI part : parts) {
                this.modelReferencesMap.remove(part);
            }
        }

        this.modelReferencesMap.remove(operation);
    }

    /**
     * This method will be called when the server is being shutdown.
     * Ensure a clean shutdown.
     */
    @Override
    public void shutdown() {
        super.shutdown();
    }
}