it.uniroma2.art.coda.provisioning.ComponentIndex Maven / Gradle / Ivy
The 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;
}
}