All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.uniroma2.art.coda.provisioning.ComponentIndex Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
package it.uniroma2.art.coda.provisioning;

import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.ClassUtils.Interfaces;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.impl.TreeModel;
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFWriter;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;

import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

import it.uniroma2.art.coda.filter.IsAssignableToPredicate;
import it.uniroma2.art.coda.interfaces.CODAContext;
import it.uniroma2.art.coda.interfaces.Converter;
import it.uniroma2.art.coda.interfaces.annotations.converters.DatatypeCapability;
import it.uniroma2.art.coda.interfaces.annotations.converters.Description;
import it.uniroma2.art.coda.interfaces.annotations.converters.FeaturePathArgument;
import it.uniroma2.art.coda.interfaces.annotations.converters.Parameter;
import it.uniroma2.art.coda.interfaces.annotations.converters.RDFCapability;
import it.uniroma2.art.coda.interfaces.annotations.converters.RDFCapabilityType;
import it.uniroma2.art.coda.interfaces.annotations.converters.RequirementLevels;
import it.uniroma2.art.coda.provisioning.impl.ConverterContractDescriptionImpl;
import it.uniroma2.art.coda.provisioning.impl.ConverterDescriptionImpl;
import it.uniroma2.art.coda.provisioning.impl.JavaTypeDescriptionImpl;
import it.uniroma2.art.coda.provisioning.impl.ParameterDescriptionImpl;
import it.uniroma2.art.coda.provisioning.impl.SignatureDescriptionImpl;
import it.uniroma2.art.coda.vocabulary.CODAONTO;

/**
 * @author Manuel Fiorelli
 *
 */
public class ComponentIndex {
	private static final Logger logger = LoggerFactory.getLogger(ComponentIndex.class);

	// Map each contract interface to its description object
	private final Map, ConverterContractDescription> contractClass2descriptionMap = new ConcurrentHashMap<>();

	// Map each converter class to its description object
	private final Map, ConverterDescription> converterClass2descriptionMap = new ConcurrentHashMap<>();

	// Reverse mapping from contract classes to converter classes
	private final Multimap, Class> contract2coverters = HashMultimap.create();

	// Map contract URIs to the corresponding contract interface
	private final Map> uri2contractMap = new HashMap<>();

	public Collection listConverters() {
		return converterClass2descriptionMap.values();
	}

	public Collection listConverterContracts() {
		return contractClass2descriptionMap.values();
	}

	public void writeRDF(Writer writer) {
		Model model = new TreeModel();
		model.setNamespace(DCTERMS.NS);
		model.setNamespace(RDF.NS);
		model.setNamespace(RDFS.NS);
		model.setNamespace(CODAONTO.NS);
		for (ConverterContractDescription converterContract : contractClass2descriptionMap.values()) {
			converterContract.toRDF(model);
		}

		for (ConverterDescription converter : converterClass2descriptionMap.values()) {
			converter.toRDF(model);
		}

		RDFWriter rdfWriter = Rio.createWriter(RDFFormat.TURTLE, writer);
		rdfWriter.set(BasicWriterSettings.PRETTY_PRINT, true);
		Rio.write(model, rdfWriter);
	}

	public synchronized ConverterDescription indexConverter(Class converterClazz)
			throws ComponentIndexingException {
		RDFCapabilityType converterRdfCapability = computeRDFCapability(converterClazz);
		Set converterDatatypes = getContractDatatypes(converterClazz);
		Collection converterSignatureDescriptions = computeSignatureDescriptions(
				converterClazz);

		String converterURI;
		try {
			Field f = converterClazz.getField(Converter.STATIC_FIELD_CONVERTER_URI);
			converterURI = (String) f.get(null);
		} catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {
			throw new ComponentIndexingException("Could not access static field telling the converter URI",
					e);
		}

		List implementedContracts = indexContracts(converterClazz);

		ConverterDescription converterDescription = new ConverterDescriptionImpl(converterURI,
				converterClazz.getSimpleName(), getContractDescription(converterClazz),
				converterRdfCapability, converterDatatypes, implementedContracts,
				converterSignatureDescriptions);

		converterClass2descriptionMap.put(converterClazz, converterDescription);
		return converterDescription;
	}

	public synchronized void forgetConverter(Class converterClazz) {
		ConverterDescription converterDescription = converterClass2descriptionMap.get(converterClazz);
		if (converterDescription == null) {
			logger.warn("Attempt to forget a converter that was not in the index {}", converterClazz);
			return;
		}

		for (ConverterContractDescription contractDescr : converterDescription.getImplementedContracts()) {
			String contractURI = contractDescr.getContractURI();

			Class contractClazz = uri2contractMap.get(contractURI);
			if (contractClazz == null) {
				logger.warn("Converter being forgotten {} references no longer indexed contract {}",
						converterDescription.getContractURI(), contractDescr.getContractURI());
				continue;
			}

			contract2coverters.remove(contractClazz, converterClazz);

			// A contract is no longer used by any converter, then garbage collect its description
			if (!contract2coverters.containsKey(contractClazz)) {
				uri2contractMap.remove(contractURI);
				contractClass2descriptionMap.remove(contractClazz);
			}
		}

		// As last step, remove the description of the converter
		converterClass2descriptionMap.remove(converterClazz);

	}

	private List indexContracts(Class converterClazz)
			throws ComponentNameConflictException {
		Collection> contractInterfaces = Collections2.filter(
				Arrays.asList(converterClazz.getInterfaces()),
				Predicates.and(IsAssignableToPredicate.getFilter(Converter.class),
						Predicates.not(Predicates.>equalTo(Converter.class))));

		List implemetedContracts = new ArrayList<>();

		for (Class aContractInterface : contractInterfaces) {
			Optional converterContractDescriptionHolder = indexIfContract(
					aContractInterface);
			converterContractDescriptionHolder.ifPresent(implemetedContracts::add);
		}

		return implemetedContracts;
	}

	private Optional indexIfContract(Class aContractInterface)
			throws ComponentNameConflictException {
		Field f;
		Object contractURIObject;

		try {
			f = aContractInterface.getField(Converter.STATIC_FIELD_CONTRACT_URI);
			contractURIObject = f.get(null);
		} catch (NoSuchFieldException | SecurityException | IllegalArgumentException
				| IllegalAccessException e) {
			return Optional.empty();
		}

		if (!(contractURIObject instanceof String))
			return Optional.empty();

		String contractURI = (String) contractURIObject;

		Class alreadyRegisteredContract = uri2contractMap.get(contractURI);
		// If a contract has been already registered under the URI of the contract being indexed...
		if (alreadyRegisteredContract != null) {
			if (alreadyRegisteredContract != aContractInterface) { // If the two contracts differ, exception
				throw new ComponentNameConflictException(contractURI);
			} else { // Otherwise, that is the same contract pulled previously by a differnet converter
				return Optional.of(contractClass2descriptionMap.get(alreadyRegisteredContract));
			}
		}
		RDFCapabilityType rdfCapability = computeRDFCapability(aContractInterface);
		Set datatypes = getContractDatatypes(aContractInterface);
		Collection signatureDescriptions = computeSignatureDescriptions(
				aContractInterface);

		ConverterContractDescription contractDescription = new ConverterContractDescriptionImpl(contractURI,
				aContractInterface.getSimpleName(), getContractDescription(aContractInterface), rdfCapability,
				datatypes, signatureDescriptions);

		uri2contractMap.put(contractURI, aContractInterface);
		contractClass2descriptionMap.put(aContractInterface, contractDescription);

		return Optional.of(contractDescription);
	}

	/**
	 * Computes the signature of a contract or converter method. Annotations of type {@link Parameter} on
	 * parameters are taken from the definition closest to the given method.
	 * 
	 * @return
	 */
	private Collection computeSignatureDescriptions(Class aContractInterface) {

		Collection signatureDescriptions = new ArrayList<>();

		Method[] methods = aContractInterface.getMethods();

		for (Method aMethod : methods) {
			RequirementLevels featurePathRequirementLevel = computeFeaturePathRequirementLevel(aMethod);
			List parameterDescriptions = new ArrayList<>();

			String methodName = aMethod.getName();

			Type[] mandatoryParameterTypes;

			final boolean producingURI;

			if (methodName.equals("produceURI")) {
				mandatoryParameterTypes = new Type[] { CODAContext.class, String.class };
				producingURI = true;
			} else if (methodName.equals("produceLiteral")) {
				mandatoryParameterTypes = new Type[] { CODAContext.class, String.class, String.class,
						String.class };
				producingURI = false;
			} else {
				continue; // not a converter method
			}

			Type[] parameterTypes = aMethod.getGenericParameterTypes();

			if (parameterTypes.length < mandatoryParameterTypes.length) {
				logger.warn("Method {} is not a valid signature. Less paramters than expected", aMethod);
				continue; // not valid
			}

			for (int i = 0; i < mandatoryParameterTypes.length; i++) {
				if (!mandatoryParameterTypes[i].equals(parameterTypes[i])) {
					logger.warn("Missing mandatory parameter in method {} at index {}", aMethod, i);
					continue;
				}
			}

			java.lang.reflect.Parameter[] parameters = aMethod.getParameters();

			Set overrideHierarchy = MethodUtils.getOverrideHierarchy(aMethod, Interfaces.INCLUDE);

			for (int j = mandatoryParameterTypes.length; j < parameters.length; j++) {
				java.lang.reflect.Parameter aParameter = parameters[j];

				Type paramType = aParameter.getParameterizedType();
				Parameter parameterAnnotation = null;
				for (Method overriddenMehod : overrideHierarchy) {
					parameterAnnotation = overriddenMehod.getParameters()[j].getAnnotation(Parameter.class);
					if (parameterAnnotation != null)
						break;
				}

				String paramName = "arg-" + (j - mandatoryParameterTypes.length);
				String paramHtmlDescription = "";

				if (parameterAnnotation != null) {
					paramName = parameterAnnotation.name();
					paramHtmlDescription = parameterAnnotation.htmlDescription();
				}

				ParameterDescription parameterDescription = new ParameterDescriptionImpl(paramName,
						paramHtmlDescription, new JavaTypeDescriptionImpl(paramType));

				parameterDescriptions.add(parameterDescription);
			}

			TypeDescription returnTypeDescription = new JavaTypeDescriptionImpl(
					aMethod.getGenericReturnType());
			signatureDescriptions.add(new SignatureDescriptionImpl(producingURI, returnTypeDescription,
					parameterDescriptions, featurePathRequirementLevel));

		}

		return signatureDescriptions;
	}

	/**
	 * Computes the feature path requirement level of a contract or converter method. A sub-contract (or
	 * sub-converter) should only loose the feature path requirement level. Therefore, pick the definition
	 * closest to the contract interface.
	 * 
	 * @return
	 */
	private RequirementLevels computeFeaturePathRequirementLevel(Method aMethod) {
		FeaturePathArgument featurePathArgumentAnnot = AnnotationUtils.findAnnotation(aMethod,
				FeaturePathArgument.class);
		if (featurePathArgumentAnnot != null) {
			return featurePathArgumentAnnot.requirementLevel();
		} else {
			return RequirementLevels.REQUIRED;
		}
	}

	private Set getContractDatatypes(Class aContractInterface) {
		DatatypeCapability meta = aContractInterface.getAnnotation(DatatypeCapability.class);
		if (meta == null) {
			return Collections.emptySet();
		} else {
			Set datatypes = new HashSet();

			for (String dt : meta.value()) {
				datatypes.add(SimpleValueFactory.getInstance().createIRI(dt));
			}

			return datatypes;
		}
	}

	private String getContractDescription(Class aContractInterface) {
		Description meta = aContractInterface.getAnnotation(Description.class);
		if (meta == null) {
			return "";
		} else {
			return meta.value();
		}
	}

	/**
	 * Computes the RDF capability of a converter contract. A sub-contract (or sub-converter) should only
	 * reduce the RDF capabilities. Therefore, pick the definition closest to the contract interface.
	 * 
	 * @return
	 */
	private RDFCapabilityType computeRDFCapability(Class aContractInterface) {
		// Find a possibly inherited annotation
		RDFCapability rdfCapabilityAnnot = AnnotationUtils.findAnnotation(aContractInterface,
				RDFCapability.class);

		if (rdfCapabilityAnnot != null) {
			return rdfCapabilityAnnot.value();
		}

		// If none is found, computes a default value
		boolean produceLiteral = false;
		boolean produceUri = false;

		for (Method m : aContractInterface.getMethods()) {
			if (m.getName().equals("produceLiteral")) {
				produceLiteral = true;
			} else if (m.getName().equals("produceURI")) {
				produceUri = true;
			}
		}

		if (produceLiteral && produceUri) {
			return RDFCapabilityType.node;
		} else if (produceLiteral) {
			return RDFCapabilityType.literal;
		} else if (produceUri) {
			return RDFCapabilityType.uri;
		}

		// This is a wired condition, since there is no produceLiteral nor produceURI
		return RDFCapabilityType.node;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy