blob: 3932afc3fa249065614f954d990cda29545b54f0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.stanbol.enhancer.rdfentities.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.clerezza.rdf.core.LiteralFactory;
import org.apache.clerezza.rdf.core.NoConvertorException;
import org.apache.clerezza.rdf.core.NonLiteral;
import org.apache.clerezza.rdf.core.Resource;
import org.apache.clerezza.rdf.core.Triple;
import org.apache.clerezza.rdf.core.TypedLiteral;
import org.apache.clerezza.rdf.core.UriRef;
import org.apache.clerezza.rdf.core.impl.TripleImpl;
import org.apache.stanbol.enhancer.rdfentities.Rdf;
import org.apache.stanbol.enhancer.rdfentities.RdfEntity;
import org.apache.stanbol.enhancer.servicesapi.rdf.Properties;
public class RdfProxyInvocationHandler implements InvocationHandler {
/**
* The getID method of the RdfEntity Interface
*/
protected static final Method getIDMethod;
/**
* The toString Method of {@link Object}
*/
protected static final Method toString;
/**
* The equals Method of {@link Object}
*/
protected static final Method equals;
/**
* The hashCode Method of {@link Object}
*/
protected static final Method hashCode;
static {
try {
getIDMethod = RdfEntity.class.getMethod("getId", new Class<?>[]{});
toString = Object.class.getMethod("toString", new Class<?>[]{});
equals = Object.class.getMethod("equals", new Class<?>[]{Object.class});
hashCode = Object.class.getMethod("hashCode", new Class<?>[]{});
} catch (SecurityException e) {
throw new IllegalStateException("Unable to access getId Method in the "+RdfEntity.class+" Interface",e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Unable to find getId Method in the "+RdfEntity.class+" Interface",e);
}
}
/**
* The logger TODO: Question: How to get the dependencies for logging working with maven :(
*/
// private static final Logger log = LoggerFactory.getLogger(RdfProxyInvocationHandler.class);
protected SimpleRdfEntityFactory factory;
protected LiteralFactory literalFactory;
protected NonLiteral rdfNode;
private final Set<Class<?>> interfaces;
public RdfProxyInvocationHandler(SimpleRdfEntityFactory factory, NonLiteral rdfNode, Class<?>[] parsedInterfaces, LiteralFactory literalFactory){
this.rdfNode = rdfNode;
this.factory = factory;
this.literalFactory = literalFactory;
//TODO If slow implement this by directly using the MGraph Interface!
Collection<UriRef> nodeTypes = getValues(Properties.RDF_TYPE, UriRef.class);
Set<Class<?>> interfaceSet = new HashSet<Class<?>>();
for (Class<?> clazz : parsedInterfaces){
if(!clazz.isInterface()){
throw new IllegalStateException("Parsed Class "+clazz+" is not an interface!");
}
interfaceSet.add(clazz);
getSuperInterfaces(clazz, interfaceSet);
}
this.interfaces = Collections.unmodifiableSet(interfaceSet); //nobody should be able to change this!
for (Class<?> clazz : this.interfaces){
Rdf classAnnotation = clazz.getAnnotation(Rdf.class);
if(classAnnotation == null){
} else { //check of the type statement is present
UriRef typeRef = new UriRef(classAnnotation.id());
if(!nodeTypes.contains(typeRef)){
//TODO: Question: How to get the dependencies for logging working with maven :(
//log.debug("add type "+typeRef+" for interface "+clazz+" to node "+rdfNode);
addValue(Properties.RDF_TYPE, typeRef); //add the missing type!
} else {
// else the type is already present ... nothing to do
//TODO: Question: How to get the dependencies for logging working with maven :(
//log.debug("type "+typeRef+" for interface "+clazz+" is already present for node "+rdfNode);
}
}
}
}
private static void getSuperInterfaces(Class<?> interfaze, Collection<Class<?>> interfaces){
for (Class<?> superInterface : interfaze.getInterfaces()){
if(superInterface != null){
interfaces.add(superInterface);
getSuperInterfaces(superInterface, interfaces); //recursive
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//RdfEntity rdfEntity;
if(!(proxy instanceof RdfEntity)){
throw new IllegalArgumentException("Parsed proxy instance is not of type "+RdfEntity.class
+". This RdfWrapperInvocationHandler implementations only work for proxies implementing this interface!");
}
//First check for Methods defined in RDFEntity and java.lang.Object
//implementation of the RffEntity Interface method!
if(method.equals(getIDMethod)){
return rdfNode;
}
//implement toString
if(method.equals(equals)){
return args[0] != null && args[0] instanceof RdfEntity && ((RdfEntity)args[0]).getId().equals(rdfNode);
}
//implement hashCode
if(method.equals(hashCode)){
return rdfNode.toString().hashCode();
}
//implement toString
if(method.equals(toString)){
return "Proxy for Node "+rdfNode+" and interfaces "+interfaces;
}
Rdf rdf = method.getAnnotation(Rdf.class);
if(rdf == null){
throw new IllegalStateException("Invoked Method does not have an Rdf annotation!");
}
UriRef property;
if(rdf.id().startsWith("http://") || rdf.id().startsWith("urn:")){
property = new UriRef(rdf.id());
} else {
throw new IllegalStateException("The id=\""+rdf.id()+"\"provided by the rdf annotation is not an valid URI");
}
//check for Write (Setter) Method
if(method.getReturnType().equals(void.class)){
Type[] parameterTypes = method.getGenericParameterTypes();
//check the parameter types to improve error messages!
//Only methods with a single parameter are supported
if(parameterTypes.length!=1){
throw new IllegalStateException("Unsupported parameters for Method "+method.toString()
+"! Only setter methodes with a singe parameter are supported.");
}
final Type parameterType = parameterTypes[0];
//now check if args != null and has an element
if(args == null){
throw new IllegalArgumentException(
"NULL parsed as \"Object[] args\". An array with a single value is expected when calling "+method.toString()+"!");
}
if(args.length<1){
throw new IllegalArgumentException(
"An empty array was parsed as \"Object[] args\". An array with a single value is expected when calling method "+method.toString()+"!");
}
final Object value = args[0];
//Handle Arrays
if(parameterType instanceof Class<?> && ((Class<?>)parameterType).isArray()){
throw new IllegalStateException("No support for Arrays right now. Use "+Collection.class+" instead");
}
//if null is parsed as value we need to delete all values
if (value == null){
removeValues(property);
return null; //setter methods are void -> return null
}
//if a collection is parsed we need to check the generic type
if (Collection.class.isAssignableFrom(value.getClass())){
Type genericType = null;
if (parameterTypes[0] instanceof ParameterizedType){
for(Type typeArgument : ((ParameterizedType)parameterTypes[0]).getActualTypeArguments()){
if (genericType == null){
genericType = typeArgument;
} else {
//TODO: replace with a warning but for testing start with an exception
throw new IllegalStateException(
"Multiple generic type definition for method "+method.toString()
+" (generic types: "+((ParameterizedType) parameterTypes[0]).getActualTypeArguments()+")");
}
}
}
setValues(property, (Collection<?>) value);
return null;
} else {
setValue(property, value);
return null;
}
} else { //assume an read (getter) method
Class<?> returnType = method.getReturnType();
if (Collection.class.isAssignableFrom(returnType)){
Type genericType = null;
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) genericReturnType;
for (Type typeArgument : type.getActualTypeArguments()){
if (genericType == null){
genericType = typeArgument;
} else {
//TODO: replace with a warning but for testing start with an exception
throw new IllegalStateException(
"Multiple generic type definition for method "+method.toString()
+" (generic types: "+type.getActualTypeArguments()+")");
}
}
}
if (genericType == null){
throw new IllegalStateException(
"Generic Type not defined for Collection in Method "+method.toString()
+" (generic type is needed to correctly map rdf values for property "+property);
}
return getValues(property, (Class<?>)genericType);
} else {
return getValue(property, returnType);
}
}
}
@SuppressWarnings("unchecked")
private <T> T getValue(UriRef property, Class<T> type){
Iterator<Triple> results = factory.getGraph().filter(rdfNode, property, null);
if (results.hasNext()){
Resource result = results.next().getObject();
if (result instanceof NonLiteral){
if (RdfEntity.class.isAssignableFrom(type)){
return (T)factory.getProxy((NonLiteral)result, (Class<? extends RdfEntity>)type);
} else { //check result for UriRef and types UriRef, URI or URL
if(result instanceof UriRef){
if (UriRef.class.isAssignableFrom(type)){
return (T)result;
} else if (URI.class.isAssignableFrom(type)){
try {
return (T)new URI(((UriRef)result).getUnicodeString());
} catch (URISyntaxException e) {
throw new IllegalStateException("Unable to parse "+URI.class
+" for "+UriRef.class+" value="+((UriRef)result).getUnicodeString());
}
} else if (URL.class.isAssignableFrom(type)){
try {
return (T)new URL(((UriRef)result).getUnicodeString());
} catch (MalformedURLException e) {
throw new IllegalStateException("Unable to parse "+URL.class
+" for "+UriRef.class+" value="+((UriRef)result).getUnicodeString());
}
} else {
throw new IllegalArgumentException("Parsed Type "+type
+" is not compatible for result type "+result.getClass()
+" (value "+result+") of node "+rdfNode+" and property "+property
+"! (Subclass of RdfEntity, UriRef, URI or URL is expected for NonLiteral Values)");
}
} else {
throw new IllegalArgumentException("Parsed Type "+type
+" is not compatible for result type "+result.getClass()
+" (value "+result+") of node "+rdfNode+" and property "+property
+"! (Subclass of RdfEntity expected as type for NonLiteral values that are no instanceof UriRef)");
}
}
} else {
return literalFactory.createObject(type,(TypedLiteral) result);
}
} else {
return null;
}
}
private <T> Collection<T> getValues(UriRef property, Class<T> type){
return new RdfProxyPropertyCollection<T>(property, type);
}
private void setValue(UriRef property, Object value){
removeValues(property);
addValue(property, value);
}
private void setValues(UriRef property, Collection<?> values){
removeValues(property);
for(Object value : values){
addValue(property, value);
}
}
protected Resource getRdfResource(Object value) throws NoConvertorException{
if(value instanceof Resource){
//if the parsed object is already a Resource
return (Resource) value; //return it
} else if(value instanceof RdfEntity){ //check for other proxies
return ((RdfEntity)value).getId();
} else if(value instanceof URI){ //or URI links
return new UriRef(value.toString());
} else if(value instanceof URL){ //or URL links
return new UriRef(value.toString());
} else { //nothing of that
//try to make an Literal (Clarezza internal Adapters)
return literalFactory.createTypedLiteral(value);
}
}
private boolean addValue(UriRef property, Object value){
Resource rdfValue;
try {
rdfValue = getRdfResource(value);
return factory.getGraph().add(new TripleImpl(rdfNode, property, rdfValue));
} catch (NoConvertorException e){
throw new IllegalArgumentException("Unable to transform "+value.getClass()
+" to an RDF Node. Only "+RdfEntity.class+" and RDF Literal Types are supported");
}
}
private boolean removeValue(UriRef property, Object value){
Resource rdfValue;
try {
rdfValue = getRdfResource(value);
return factory.getGraph().remove(new TripleImpl(rdfNode, property, rdfValue));
} catch (NoConvertorException e){
throw new IllegalArgumentException("Unable to transform "+value.getClass()
+" to an RDF Node. Only "+RdfEntity.class+" and RDF Literal Types are supported");
}
}
private void removeValues(UriRef proptery){
Iterator<Triple> toRemove = factory.getGraph().filter(rdfNode, proptery, null);
while(toRemove.hasNext()){
factory.getGraph().remove(toRemove.next());
}
}
/**
* We need this class to apply changes in the collection to the MGraph.
* This collection implementation is a stateless wrapper over the
* triples selected by the subject,property pair over the MGraph!<br>
* Default implementation of {@link AbstractCollection} are very poor
* performance. Because of that this class overrides some methods
* already implemented by its abstract super class.
* @author westei
*
* @param <T>
*/
private class RdfProxyPropertyCollection<T> extends AbstractCollection<T> {
//private final NonLiteral resource;
private final UriRef property;
private final Class<T> genericType;
private final boolean entity;
private final boolean uri;
private final boolean url;
private final boolean uriRef;
private RdfProxyPropertyCollection(UriRef property,Class<T> genericType) {
this.property = property;
this.genericType = genericType;
entity = RdfEntity.class.isAssignableFrom(genericType);
uri = URI.class.isAssignableFrom(genericType);
url = URL.class.isAssignableFrom(genericType);
uriRef = UriRef.class.isAssignableFrom(genericType);
}
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
Iterator<Triple> results = factory.getGraph().filter(rdfNode, property, null);
@Override
public boolean hasNext() {
return results.hasNext();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
Resource value = results.next().getObject();
if (entity){
//type checks are done within the constructor
return (T) factory.getProxy((NonLiteral)value, (Class<? extends RdfEntity>)genericType);
} else if(uri){
try {
return (T)new URI(((UriRef)value).getUnicodeString());
} catch (URISyntaxException e) {
throw new IllegalStateException("Unable to parse "+URI.class+" for "+UriRef.class+" value="+((UriRef)value).getUnicodeString());
}
} else if(url){
try {
return (T)new URL(((UriRef)value).getUnicodeString());
} catch (MalformedURLException e) {
throw new IllegalStateException("Unable to parse "+URL.class+" for "+UriRef.class+" value="+((UriRef)value).getUnicodeString());
}
} else if(uriRef){
return (T)value;
} else {
return literalFactory.createObject(genericType, (TypedLiteral)value);
}
}
@Override
public void remove() {
results.remove(); //no Idea if Clerezza implements that ^
}
};
}
@Override
public int size() {
Iterator<Triple> results = factory.getGraph().filter(rdfNode, property, null);
int size = 0;
for (;results.hasNext();size++){
results.next();
}
return size;
}
public boolean add(T value) {
return addValue(property, value);
}
@Override
public boolean remove(Object value) {
return removeValue(property,value);
}
@Override
public boolean isEmpty() {
return !factory.getGraph().filter(rdfNode, property, null).hasNext();
}
}
}