/*
* Copyright (c) 2009-2012 Clark & Parsia, LLC. <http://www.clarkparsia.com>
*
* 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 com.clarkparsia.empire.annotation;
import com.complexible.common.openrdf.model.Graphs;
import com.google.common.base.Optional;
import com.google.common.collect.ObjectArrays;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.Value;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.Statement;
import org.openrdf.model.Graph;
import org.openrdf.model.util.GraphUtil;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.model.vocabulary.RDFS;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Date;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.Locale;
import java.util.ArrayList;
import java.net.URISyntaxException;
import org.openrdf.model.impl.ValueFactoryImpl;
import com.clarkparsia.empire.ds.DataSource;
import com.clarkparsia.empire.ds.DataSourceException;
import com.clarkparsia.empire.ds.QueryException;
import com.clarkparsia.empire.ds.DataSourceUtil;
import com.clarkparsia.empire.EmpireOptions;
import com.clarkparsia.empire.EmpireGenerated;
import com.clarkparsia.empire.SupportsRdfId;
import com.clarkparsia.empire.Empire;
import com.clarkparsia.empire.Dialect;
import com.clarkparsia.empire.annotation.runtime.Proxy;
import com.clarkparsia.empire.impl.serql.SerqlDialect;
import static com.clarkparsia.empire.util.BeanReflectUtil.set;
import static com.clarkparsia.empire.util.BeanReflectUtil.setAccessible;
import static com.clarkparsia.empire.util.BeanReflectUtil.getAnnotatedFields;
import static com.clarkparsia.empire.util.BeanReflectUtil.getAnnotatedGetters;
import static com.clarkparsia.empire.util.BeanReflectUtil.getAnnotatedSetters;
import static com.clarkparsia.empire.util.BeanReflectUtil.get;
import com.clarkparsia.empire.util.BeanReflectUtil;
import com.clarkparsia.empire.util.EmpireUtil;
import static com.clarkparsia.empire.util.EmpireUtil.asPrimaryKey;
import com.complexible.common.openrdf.util.ResourceBuilder;
import com.complexible.common.openrdf.util.GraphBuilder;
import com.complexible.common.util.PrefixMapping;
import com.complexible.common.base.Strings2;
import com.complexible.common.base.Dates;
import com.complexible.common.net.NetUtils;
import com.complexible.common.collect.Iterables2;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.inject.ProvisionException;
import com.google.inject.ConfigurationException;
import javax.persistence.Entity;
import javax.persistence.Transient;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyObject;
import javassist.util.proxy.MethodFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl;
/**
* <p>Description: Utility for creating RDF from a compliant Java Bean, and for turning RDF (the results of a describe
* on a given rdf:ID into a KB) into a Java bean.</p>
* <p>Usage:<br/>
* <code><pre>
* MyClass aObj = new MyClass();
*
* // set some data on the object
* KB.add(RdfGenerator.toRdf(aObj));
*
* MyClass aObjCopy = RdfGenerator.fromRdf(MyClass.class, aObj.getRdfId(), KB);
*
* // this will print true
* System.out.println(aObj.equals(aObjCopy));
* </pre>
* </code>
* </p>
* <p>
* Compliant classes must be annotated with the {@link Entity} JPA annotation, the {@link RdfsClass} annotation,
* and must implement the {@link SupportsRdfId} interface.</p>
*
* @author Michael Grove
* @since 0.1
* @version 0.7.3
*/
public final class RdfGenerator {
/**
* Global ValueFactory to use for converting Java values into sesame objects for serialization to RDF
*/
private static final ValueFactory FACTORY = new ValueFactoryImpl();
private static final ContainsResourceValues CONTAINS_RESOURCES = new ContainsResourceValues();
private static final LanguageFilter LANG_FILTER = new LanguageFilter(getLanguageForLocale());
/**
* The logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(RdfGenerator.class.getName());
/**
* Map from rdf:type URI's to the Java class which corresponds to that resource.
*/
private final static Multimap<URI, Class> TYPE_TO_CLASS = HashMultimap.create();
/**
* Map to keep a record of what instances are currently being created in order to prevent cycles. Keys are the
* identifiers of the instances and the values are the instances
*/
public final static Map<Object, Object> OBJECT_M = new HashMap<Object, Object>();
private final static Set<Class<?>> REGISTERED_FOR_NS = new HashSet<Class<?>>();
/**
* Cache the AccessibleObjects to avoid repeated inspections
*/
private final static Map<Class<?>,Map<URI,AccessibleObject>> ACCESSORS_BY_CLASS = new HashMap<Class<?>, Map<URI, AccessibleObject>>();
/**
* Initialize some parameters in the RdfGenerator. This caches namespace and type mapping information locally
* which will be used in subsequent rdf generation requests.
* @param theClasses the list of classes to be handled by the RdfGenerator
*/
public static synchronized void init(Collection<Class<?>> theClasses) {
for (Class<?> aClass : theClasses) {
RdfsClass aAnnotation = aClass.getAnnotation(RdfsClass.class);
if (aAnnotation != null) {
addNamespaces(aClass);
TYPE_TO_CLASS.put(FACTORY.createURI(PrefixMapping.GLOBAL.uri(aAnnotation.value())), aClass);
}
}
}
/**
* Create an instance of the specified class and instantiate it's data from the given data source using the RDF
* instance specified by the given URI
* @param theClass the class to create
* @param theKey the id of the RDF individual containing the data for the new instance
* @param theSource the KB to get the RDF data from
* @param <T> the type of the instance to create
* @return a new instance
* @throws InvalidRdfException thrown if the class does not support RDF JPA operations, or does not provide sufficient access to its fields/data.
* @throws DataSourceException thrown if there is an error while retrieving data from the graph
*/
public static <T> T fromRdf(Class<T> theClass, String theKey, DataSource theSource) throws InvalidRdfException, DataSourceException {
return fromRdf(theClass, EmpireUtil.asPrimaryKey(theKey), theSource);
}
/**
* Create an instance of the specified class and instantiate it's data from the given data source using the RDF
* instance specified by the given URI
* @param theClass the class to create
* @param theURI the id of the RDF individual containing the data for the new instance
* @param theSource the KB to get the RDF data from
* @param <T> the type of the instance to create
* @return a new instance
* @throws InvalidRdfException thrown if the class does not support RDF JPA operations, or does not provide sufficient access to its fields/data.
* @throws DataSourceException thrown if there is an error while retrieving data from the graph
*/
public static <T> T fromRdf(Class<T> theClass, java.net.URI theURI, DataSource theSource) throws InvalidRdfException, DataSourceException {
return fromRdf(theClass, new SupportsRdfId.URIKey(theURI), theSource);
}
/**
* Create an instance of the specified class and instantiate it's data from the given data source using the RDF
* instance specified by the given URI
* @param theClass the class to create
* @param theId the id of the RDF individual containing the data for the new instance
* @param theSource the KB to get the RDF data from
* @param <T> the type of the instance to create
* @return a new instance
* @throws InvalidRdfException thrown if the class does not support RDF JPA operations, or does not provide sufficient access to its fields/data.
* @throws DataSourceException thrown if there is an error while retrieving data from the graph
*/
public static <T> T fromRdf(Class<T> theClass, SupportsRdfId.RdfKey theId, DataSource theSource) throws InvalidRdfException, DataSourceException {
T aObj;
long start = System.currentTimeMillis();
try {
aObj = Empire.get().instance(theClass);
}
catch (ConfigurationException ex) {
aObj = null;
}
catch (ProvisionException ex) {
aObj = null;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Tried to get instance of class in {} ms ", (System.currentTimeMillis()-start ));
}
start = System.currentTimeMillis();
if (aObj == null) {
// this means Guice construction failed, which is not surprising since that's not going to be the default.
// so we'll try our own reflect based creation or create bytecode for an interface.
try {
long istart = System.currentTimeMillis();
if (theClass.isInterface() || Modifier.isAbstract(theClass.getModifiers())) {
aObj = com.clarkparsia.empire.codegen.InstanceGenerator.generateInstanceClass(theClass).newInstance();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("CodeGenerated instance in {} ms. ", (System.currentTimeMillis() - istart));
}
}
else {
aObj = theClass.newInstance();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("CodeGenerated instance in {} ms. ", (System.currentTimeMillis() - istart));
}
}
}
catch (InstantiationException e) {
throw new InvalidRdfException("Cannot create instance of bean, should have a default constructor.", e);
}
catch (IllegalAccessException e) {
throw new InvalidRdfException("Could not access default constructor for class: " + theClass, e);
}
catch (Exception e) {
throw new InvalidRdfException("Cannot create an instance of bean", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Got reflect instance of class {} ms ", (System.currentTimeMillis()-start ) );
}
start = System.currentTimeMillis();
}
asSupportsRdfId(aObj).setRdfId(theId);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Has rdfId {} ms", (System.currentTimeMillis()-start ));
}
start = System.currentTimeMillis();
Class<T> aNewClass = determineClass(theClass, aObj, theSource);
if (!aNewClass.equals(aObj.getClass())) {
try {
aObj = aNewClass.newInstance();
}
catch (InstantiationException e) {
throw new InvalidRdfException("Cannot create instance of bean, should have a default constructor.", e);
}
catch (IllegalAccessException e) {
throw new InvalidRdfException("Could not access default constructor for class: " + theClass, e);
}
catch (Exception e) {
throw new InvalidRdfException("Cannot create an instance of bean", e);
}
asSupportsRdfId(aObj).setRdfId(theId);
}
return fromRdf(aObj, theSource);
}
@SuppressWarnings("unchecked")
private static <T> Class<T> determineClass(Class<T> theOrigClass, T theObj, DataSource theSource) throws InvalidRdfException, DataSourceException {
Class aResult = theOrigClass;
final SupportsRdfId aTmpSupportsRdfId = asSupportsRdfId(theObj);
// ExtGraph aGraph = new ExtGraph(DataSourceUtil.describe(theSource, theObj));
final Collection<Value> aTypes = DataSourceUtil.getValues(theSource, EmpireUtil.asResource(EmpireUtil.asSupportsRdfId(theObj)), RDF.TYPE);
// right now, our best match is the original class (we will refine later)
// final Resource aTmpRes = EmpireUtil.asResource(aTmpSupportsRdfId);
// iterate for all rdf:type triples in the data
// There may be multiple rdf:type triples, which can then translate onto multiple candidate Java classes
// some of the Java classes may belong to the same class hierarchy, whereas others can have no common
// super class (other than java.lang.Object)
for (Value aValue : aTypes) {
if (!(aValue instanceof URI)) {
// there is no URI in the object position of rdf:type
// ignore that data
continue;
}
URI aType = (URI) aValue;
for (Class aCandidateClass : TYPE_TO_CLASS.get(aType)) {
if (aCandidateClass.equals(aResult)) {
// it is mapped to the same Java class, that we have; ignore
continue;
}
// at this point we found an rdf:type triple that resolves to a different Java class than we have
// we are only going to accept this candidate class if it is a subclass of the current Java class
// (doing otherwise, may cause class cast exceptions)
if (aResult.isAssignableFrom(aCandidateClass)) {
aResult = aCandidateClass;
}
}
}
try {
if (aResult.isInterface() || Modifier.isAbstract(aResult.getModifiers()) || !EmpireGenerated.class.isAssignableFrom(aResult)) {
aResult = com.clarkparsia.empire.codegen.InstanceGenerator.generateInstanceClass(aResult);
}
}
catch (Exception e) {
throw new InvalidRdfException("Cannot generate a class for a bean", e);
}
return aResult;
}
/**
* Populate the fields of the current instance from the RDF indiviual with the given URI
* @param theObj the Java object to populate
* @param theSource the KB to get the RDF data from
* @param <T> the type of the class being populated
* @return theObj, populated from the specified DataSource
* @throws InvalidRdfException thrown if the object does not support the RDF JPA API.
* @throws DataSourceException thrown if there is an error retrieving data from the database
*/
@SuppressWarnings("unchecked")
private synchronized static <T> T fromRdf(T theObj, DataSource theSource) throws InvalidRdfException, DataSourceException {
final SupportsRdfId aTmpSupportsRdfId = asSupportsRdfId(theObj);
final SupportsRdfId.RdfKey theKeyObj = aTmpSupportsRdfId.getRdfId();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Converting {} to RDF.", theObj);
}
if (OBJECT_M.containsKey(theKeyObj)) {
// TODO: this is probably a safe cast, i dont see how something w/ the same URI, which should be the same
// object would change types
return (T) OBJECT_M.get(theKeyObj);
}
try {
OBJECT_M.put(theKeyObj, theObj);
Graph aGraph = DataSourceUtil.describe(theSource, theObj);
if (aGraph.size() == 0) {
return theObj;
}
final Resource aTmpRes = EmpireUtil.asResource(aTmpSupportsRdfId);
Set<URI> aProps = new HashSet<URI>();
Iterator<Statement> sIter = aGraph.match(aTmpRes, null, null);
while (sIter.hasNext()) {
Statement aStmt = sIter.next();
aProps.add(aStmt.getPredicate());
}
final SupportsRdfId aSupportsRdfId = asSupportsRdfId(theObj);
final EmpireGenerated aEmpireGenerated = asEmpireGenerated(theObj);
aEmpireGenerated.setAllTriples(aGraph);
OBJECT_M.put(theKeyObj, theObj);
final Resource aRes = EmpireUtil.asResource(aSupportsRdfId);
addNamespaces(theObj.getClass());
Map<URI, AccessibleObject> theCachedMap = ACCESSORS_BY_CLASS.get(theObj.getClass());
if (theCachedMap == null) {
theCachedMap = cacheAccessibles(theObj.getClass(), aRes);
}
Set<URI> aUsedProps = new HashSet<URI>();
for (URI aProp : aProps) {
AccessibleObject aAccess = theCachedMap.get(aProp);
if (aAccess == null && RDF.TYPE.equals(aProp)) {
// TODO: the following block should be entirely removed (leaving continue only)
// right now, leaving it until the code review: code review before removing the following block
// my understanding is that the following block was only necessary when having a support for a single-typed objects,
// which is no longer the case
// we can skip the rdf:type property. it's basically assigned in the @RdfsClass annotation on the
// java class, so we can figure it out later if need be. TODO: of course, if something has multiple types
// that information is lost, which is not good.
/*
URI aType = (URI) aGraph.getValue(aRes, aProp);
if (!TYPE_TO_CLASS.containsKey(aType) ||
!TYPE_TO_CLASS.get(aType).isAssignableFrom(theObj.getClass())) {
if (TYPE_TO_CLASS.containsKey(aType) && !TYPE_TO_CLASS.get(aType).getName().equals(theObj.getClass().getName())) {
// TODO: this might just be an error
LOGGER.warn("Asserted rdf:type of the individual does not match the rdf:type annotation on the object. " + aType + " " + TYPE_TO_CLASS.get(aType) + " " + theObj.getClass() + " " +TYPE_TO_CLASS.get(aType).isAssignableFrom(theObj.getClass())+ " " +TYPE_TO_CLASS.get(aType).equals(theObj.getClass()) + " " + TYPE_TO_CLASS.get(aType).getName().equals(theObj.getClass().getName()));
}
else {
// if they're not equals() or isAssignableFrom, but have the same name, this is usually
// means that the class loaders don't match. so probably not an error, so no warning.
}
}
*/
continue;
}
else if (aAccess == null) {
// this must be data that is not covered by the bean (perhaps accessible by a different view/bean for a differnent type of an individual)
continue;
}
aUsedProps.add(aProp);
ToObjectFunction aFunc = new ToObjectFunction(theSource, aRes, aAccess, aProp);
Object aValue = aFunc.apply(GraphUtil.getObjects(aGraph, aRes, aProp));
boolean aOldAccess = aAccess.isAccessible();
try {
setAccessible(aAccess, true);
set(aAccess, theObj, aValue);
}
catch (InvocationTargetException e) {
// oh crap
throw new InvalidRdfException(e);
}
catch (IllegalAccessException e) {
// this should not happen since we toggle the accessibility of the field, but we'll re-throw regardless
throw new InvalidRdfException(e);
}
catch (IllegalArgumentException e) {
// this is "likely" to happen. we'll get this exception if the rdf does not match the java. for example
// if something is specified to be an int in the java class, but it typed as a float (though down conversion
// in that case might work) the set call will fail.
// TODO: shouldnt this be an error?
LOGGER.warn("Probable type mismatch: {} {}", aValue, aAccess);
}
catch (RuntimeException e) {
// TODO: i dont like keying on a RuntimeException here to get the error condition, but since the
// Function interface does not throw anything, this is the best we can do. maybe consider a
// version of the Function interface that has a throws clause, it would make this more clear.
// this was probably an error converting from a Value to an Object
throw new InvalidRdfException(e);
}
finally {
setAccessible(aAccess, aOldAccess);
}
}
sIter = aGraph.match(aTmpRes, null, null);
Graph aInstanceTriples = Graphs.newGraph();
while (sIter.hasNext()) {
Statement aStmt = sIter.next();
if (aUsedProps.contains(aStmt.getPredicate())) {
aInstanceTriples.add(aStmt);
}
}
aEmpireGenerated.setInstanceTriples(aInstanceTriples);
return theObj;
}
finally {
OBJECT_M.remove(theKeyObj);
}
}
public static Map<URI, AccessibleObject> cacheAccessibles( Class theClass, final Resource aRes ) {
final Map<URI, AccessibleObject> aAccessMap = new HashMap<URI, AccessibleObject>();
Collection<Field> aFields = getAnnotatedFields( theClass );
Collection<Method> aMethods = getAnnotatedSetters( theClass, true );
Iterables2.each(aFields, new Predicate<Field>() {
public boolean apply(final Field theField) {
if (theField.getAnnotation(RdfProperty.class) != null) {
URI theURI = FACTORY.createURI(PrefixMapping.GLOBAL.uri(theField.getAnnotation(RdfProperty.class).value()));
aAccessMap.put( theURI, theField );
}
else {
String aBase = "urn:empire:clark-parsia:";
if (aRes instanceof URI) {
aBase = ((URI)aRes).getNamespace();
}
aAccessMap.put(FACTORY.createURI(aBase + theField.getName()),
theField);
}
return true;
}
});
Iterables2.each(aMethods, new Predicate<Method>() {
public boolean apply(final Method theMethod) {
RdfProperty aAnnotation = BeanReflectUtil.getAnnotation(theMethod, RdfProperty.class);
if (aAnnotation != null) {
URI theURI = FACTORY.createURI( PrefixMapping.GLOBAL.uri( aAnnotation.value() ) );
aAccessMap.put( theURI, theMethod );
}
return true;
}
});
ACCESSORS_BY_CLASS.put( theClass, aAccessMap );
return aAccessMap;
}
/**
* Return the RdfClass annotation on the object.
* @param theObj the object to get that annotation from
* @return the objects' RdfClass annotation
* @throws InvalidRdfException thrown if the object does not have the required annotation, does not have an @Entity
* annotation, or does not {@link SupportsRdfId support Rdf Id's}
*/
private static RdfsClass asValidRdfClass(Object theObj) throws InvalidRdfException {
if (!BeanReflectUtil.hasAnnotation(theObj.getClass(), RdfsClass.class)) {
throw new InvalidRdfException("Specified value is not an RdfsClass object");
}
if (EmpireOptions.ENFORCE_ENTITY_ANNOTATION && !BeanReflectUtil.hasAnnotation(theObj.getClass(), Entity.class)) {
throw new InvalidRdfException("Specified value is not a JPA Entity object");
}
// verify that it supports rdf id's
asSupportsRdfId(theObj);
return BeanReflectUtil.getAnnotation(theObj.getClass(), RdfsClass.class);
}
/**
* Return the object casted to {@link SupportsRdfId}
* @param theObj the object to cast
* @return the object, casted to the interface
* @throws InvalidRdfException thrown if the object does not implement the interface
*/
private static SupportsRdfId asSupportsRdfId(Object theObj) throws InvalidRdfException {
if (!(theObj instanceof SupportsRdfId)) {
throw new InvalidRdfException("Object of type '" + (theObj.getClass().getName()) + "' does not implements SupportsRdfId.");
}
else {
return (SupportsRdfId) theObj;
}
}
private static EmpireGenerated asEmpireGenerated(Object theObj) throws InvalidRdfException {
if (!(theObj instanceof EmpireGenerated)) {
throw new InvalidRdfException("Object of type '" + (theObj.getClass().getName()) + "' does not implements EmpireGenerated.");
}
else {
return (EmpireGenerated) theObj;
}
}
/**
* Given an object, return it's rdf:ID. If it already has an id, that will be returned, otherwise the id
* will either be generated from the data, using the {@link RdfId} annotation as a guide, or it will auto-generate one.
* @param theObj the object
* @return the object's rdf:Id
* @throws InvalidRdfException thrown if the object does not support the minimum to create or retrieve an rdf:ID
* @see SupportsRdfId
*/
public static Resource id(Object theObj) throws InvalidRdfException {
SupportsRdfId aSupport = asSupportsRdfId(theObj);
if (aSupport.getRdfId() != null) {
return EmpireUtil.asResource(aSupport);
}
Field aIdField = BeanReflectUtil.getIdField(theObj.getClass());
String aValue = hash(Strings2.getRandomString(10));
String aNS = RdfId.DEFAULT;
URI aURI = FACTORY.createURI(aNS + aValue);
if (aIdField != null && !aIdField.getAnnotation(RdfId.class).namespace().equals("")) {
aNS = aIdField.getAnnotation(RdfId.class).namespace();
}
if (aIdField != null) {
boolean aOldAccess = aIdField.isAccessible();
aIdField.setAccessible(true);
try {
if (aIdField.get(theObj) == null) {
throw new InvalidRdfException("id field must have a value");
}
Object aValObj = aIdField.get(theObj);
aValue = Strings2.urlEncode(aValObj.toString());
if (aValObj instanceof java.net.URI || NetUtils.isURI(aValObj.toString())) {
try {
aURI = FACTORY.createURI(aValObj.toString());
}
catch (IllegalArgumentException e) {
// sometimes sesame disagrees w/ Java about what a valid URI is. so we'll have to try
// and construct a URI from the possible fragment
aURI = FACTORY.createURI(aNS + aValue);
}
}
else {
//aValue = hash(aValObj);
aURI = FACTORY.createURI(aNS + aValue);
}
}
catch (IllegalAccessException ex) {
throw new InvalidRdfException(ex);
}
aIdField.setAccessible(aOldAccess);
}
aSupport.setRdfId(new SupportsRdfId.URIKey(java.net.URI.create(aURI.toString())));
return aURI;
}
/**
* Scan the object for {@link Namespaces} annotations and add them to the current list of known namespaces
* @param theObj the object to scan.
*/
public static void addNamespaces(Class<?> theObj) {
if (theObj == null || REGISTERED_FOR_NS.contains(theObj)) {
return;
}
REGISTERED_FOR_NS.add(theObj);
Namespaces aNS = BeanReflectUtil.getAnnotation(theObj, Namespaces.class);
if (aNS == null) {
return;
}
int aIndex = 0;
while (aIndex+1 < aNS.value().length) {
String aPrefix = aNS.value()[aIndex];
String aURI = aNS.value()[aIndex+1];
// TODO: maybe have a local version of this, this will add a global namespace, and could potentially
// overwrite global things that use the same prefix but different uris, which would be bad
PrefixMapping.GLOBAL.addMapping(aPrefix, aURI);
aIndex += 2;
}
}
/**
* Return the given Java bean as a set of RDF triples
* @param theObj the object
* @return the object represented as RDF triples
* @throws InvalidRdfException thrown if the object cannot be transformed into RDF.
*/
public static Graph asRdf(final Object theObj) throws InvalidRdfException {
if (theObj == null) {
return null;
}
Object aObj = theObj;
if (aObj instanceof ProxyHandler) {
aObj = ((ProxyHandler)aObj).mProxy.value();
}
else {
try {
if (aObj.getClass().getDeclaredField("handler") != null) {
Field aProxy = aObj.getClass().getDeclaredField("handler");
aObj = ((ProxyHandler)BeanReflectUtil.safeGet(aProxy, aObj)).mProxy.value();
}
}
catch (InvocationTargetException e) {
// this is probably an error, we know its a proxy object, but can't get the proxied object
throw new InvalidRdfException("Could not access proxy object", e);
}
catch (NoSuchFieldException e) {
// this is probably ok.
}
}
RdfsClass aClass = asValidRdfClass(aObj);
Resource aSubj = id(aObj);
addNamespaces(aObj.getClass());
GraphBuilder aBuilder = new GraphBuilder();
Collection<AccessibleObject> aAccessors = new HashSet<AccessibleObject>();
aAccessors.addAll(getAnnotatedFields(aObj.getClass()));
aAccessors.addAll(getAnnotatedGetters(aObj.getClass(), true));
try {
ResourceBuilder aRes = aBuilder.instance(aBuilder.getValueFactory().createURI(PrefixMapping.GLOBAL.uri(aClass.value())),
aSubj);
for (AccessibleObject aAccess : aAccessors) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Getting rdf for : {}", aAccess);
}
AsValueFunction aFunc = new AsValueFunction(aAccess);
if (aAccess.isAnnotationPresent(Transient.class)
|| (aAccess instanceof Field
&& Modifier.isTransient( ((Field)aAccess).getModifiers() ))) {
// transient fields or accessors with the Transient annotation do not get converted.
continue;
}
RdfProperty aPropertyAnnotation = BeanReflectUtil.getAnnotation(aAccess, RdfProperty.class);
String aBase = "urn:empire:clark-parsia:";
if (aRes instanceof URI) {
aBase = ((URI)aRes).getNamespace();
}
URI aProperty = aPropertyAnnotation != null
? aBuilder.getValueFactory().createURI(PrefixMapping.GLOBAL.uri(aPropertyAnnotation.value()))
: (aAccess instanceof Field ? aBuilder.getValueFactory().createURI(aBase + ((Field)aAccess).getName()) : null);
boolean aOldAccess = aAccess.isAccessible();
setAccessible(aAccess, true);
Object aValue = get(aAccess, aObj);
setAccessible(aAccess, aOldAccess);
if (aValue == null || aValue.toString().equals("")) {
continue;
}
else if (Collection.class.isAssignableFrom(aValue.getClass())) {
@SuppressWarnings("unchecked")
List<Value> aValueList = asList(aAccess, (Collection<?>) Collection.class.cast(aValue));
if (aValueList.isEmpty()) {
continue;
}
if (aPropertyAnnotation.isList()) {
aRes.addProperty(aProperty, aValueList);
}
else {
for (Value aVal : aValueList) {
aRes.addProperty(aProperty, aVal);
}
}
}
else {
aRes.addProperty(aProperty, aFunc.apply(aValue));
}
}
}
catch (IllegalAccessException e) {
throw new InvalidRdfException(e);
}
catch (RuntimeException e) {
throw new InvalidRdfException(e);
}
catch (InvocationTargetException e) {
throw new InvalidRdfException("Cannot invoke method", e);
}
return aBuilder.graph();
}
/**
* Transform a list of Java Objects into the corresponding RDF values
* @param theAccess the accessor for the value
* @param theCollection the collection to transform
* @return the collection as a list of RDF values
* @throws InvalidRdfException thrown if any of the values cannot be transformed
*/
private static List<Value> asList(AccessibleObject theAccess, Collection<?> theCollection) throws InvalidRdfException {
try {
return Lists.newArrayList(Collections2.transform(theCollection, new AsValueFunction(theAccess)));
}
catch (RuntimeException e) {
throw new InvalidRdfException(e.getMessage());
}
}
/**
* Return a base64 encoded md5 hash of the given object
* @param theObj the object to hash
* @return the hashed version of the object.
*/
private static String hash(Object theObj) {
return Strings2.hex(Strings2.md5(theObj.toString()));
}
/**
* Javassist {@link MethodHandler} implementation for method proxying.
*/
private static class CollectionProxyHandler implements MethodHandler {
/**
* The proxy object which wraps the instance being proxied.
*/
private CollectionProxy mProxy;
/**
* Create a new ProxyHandler
* @param theProxy the proxy object
*/
private CollectionProxyHandler(final CollectionProxy theProxy) {
mProxy = theProxy;
}
/**
* Delegates the methods to the Proxy
* @inheritDoc
*/
public Object invoke(final Object theThis, final Method theMethod, final Method theProxyMethod, final Object[] theArgs) throws Throwable {
return theMethod.invoke(mProxy.value(), theArgs);
}
}
private static class CollectionProxy {
private Collection mCollection;
private AccessibleObject mField;
private Collection<Value> theList;
private ValueToObject valueToObject;
public CollectionProxy(final AccessibleObject theField, final Collection<Value> theTheList, final ValueToObject theValueToObject) {
mField = theField;
theList = theTheList;
valueToObject = theValueToObject;
}
private void init() {
Collection<Object> aValues = BeanReflectUtil.instantiateCollectionFromField(BeanReflectUtil.classFrom(mField));
for (Value aValue : theList) {
Object aListValue = valueToObject.apply(aValue);
if (aListValue == null) {
throw new RuntimeException("Error converting a list value.");
}
aValues.add(aListValue);
}
mCollection = aValues;
}
public Collection value() {
if (mCollection == null) {
init();
theList.clear();
theList = null;
mField = null;
valueToObject = null;
}
return mCollection;
}
}
/**
* Enabling this seems to use more memory than per-object proxying (or none at all). Is javassist leaking memory?
* Experimental option, not currently used.
*/
@Deprecated
public static final boolean PROXY_COLLECTIONS = false;
/**
* Implementation of the function interface to turn a Collection of RDF values into Java bean(s).
*/
private static class ToObjectFunction implements Function<Collection<Value>, Object> {
/**
* Function to turn a single value into an object
*/
private ValueToObject valueToObject;
/**
* Reference to the Type which the values will be assigned
*/
private AccessibleObject mField;
public ToObjectFunction(final DataSource theSource, Resource theResource, final AccessibleObject theField, final URI theProp) {
valueToObject = new ValueToObject(theSource, theResource, theField, theProp);
mField = theField;
}
public Object apply(final Collection<Value> theList) {
if (theList == null || theList.isEmpty()) {
return BeanReflectUtil.instantiateCollectionFromField(BeanReflectUtil.classFrom(mField));
}
if (Collection.class.isAssignableFrom(BeanReflectUtil.classFrom(mField))) {
try {
if (PROXY_COLLECTIONS && !BeanReflectUtil.isPrimitive(refineClass(mField, BeanReflectUtil.classFrom(mField), null, null))) {
Object aColType = BeanReflectUtil.instantiateCollectionFromField(BeanReflectUtil.classFrom(mField));
ProxyFactory aFactory = new ProxyFactory();
aFactory.setInterfaces(aColType.getClass().getInterfaces());
aFactory.setSuperclass(aColType.getClass());
aFactory.setFilter(METHOD_FILTER);
Object aResult = aFactory.createClass().newInstance();
((ProxyObject) aResult).setHandler(new CollectionProxyHandler(new CollectionProxy(mField, theList, valueToObject)));
return aResult;
}
else {
Collection<Object> aValues = BeanReflectUtil.instantiateCollectionFromField(BeanReflectUtil.classFrom(mField));
for (Value aValue : theList) {
Object aListValue = valueToObject.apply(aValue);
if (aListValue == null) {
throw new RuntimeException("Error converting a list value.");
}
if (aListValue instanceof Collection) {
aValues.addAll(((Collection) aListValue));
}
else {
aValues.add(aListValue);
}
}
return aValues;
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* if not list all literals
* proceed
* else
* if not lang aware
* find non lang typed literals
* if >= 1 non lang typed literals
* proceed
* else get language based on locale
* find literals based on local lang
*
* if == 0 literals
* use original list
* else use filtered list
*
* else if lang aware
*/
Collection<Value> aList = new HashSet<Value>(theList);
if (!Iterables2.find(aList, CONTAINS_RESOURCES)) {
if (!EmpireOptions.ENABLE_LANG_AWARE) {
Collection<Value> aLangFiltered = Collections2.filter(aList, new Predicate<Value>() { public boolean apply(final Value theValue) { return ((Literal)theValue).getLanguage() == null; }});
if (aLangFiltered.isEmpty()) {
LANG_FILTER.setLangCode(getLanguageForLocale());
aLangFiltered = Collections2.filter(aList, LANG_FILTER);
}
if (!aLangFiltered.isEmpty()) {
aList = aLangFiltered;
}
}
else {
LANG_FILTER.setLangCode(mField.getAnnotation(RdfProperty.class).language());
aList = Collections2.filter(aList, LANG_FILTER);
}
}
// aList = filter(aList, new Predicate<Value>() {
// public boolean accept(final Value theValue) {
// if (theValue instanceof Resource
// || !EmpireOptions.ENABLE_LANG_AWARE
// || (EmpireOptions.ENABLE_LANG_AWARE
// && theValue instanceof Literal
// && !mField.getAnnotation(RdfProperty.class).language().equals(""))
// && mField.getAnnotation(RdfProperty.class).language().equals(((Literal)theValue).getLanguage())) {
// return true;
// }
// else {
// return false;
// }
// }
// });
if (aList.isEmpty()) {
// yes, we checked for emptiness to begin the method, but we might have done some filtering based on the
// language tags, so we need to check again.
return BeanReflectUtil.instantiateCollectionFromField(BeanReflectUtil.classFrom(mField));
}
else if ((aList.size() == 1) || (! EmpireOptions.STRICT_MODE)) {
// collection of one element, just convert the single element and send that back
return valueToObject.apply(aList.iterator().next());
}
else {
throw new RuntimeException("Cannot convert list of values to anything meaningful for the field. " + mField + " " + aList);
}
}
}
private static String getLanguageForLocale() {
return Locale.getDefault() == null || Locale.getDefault().toString().equals("")
? "en"
: (Locale.getDefault().toString().indexOf("_") != -1
? Locale.getDefault().toString().substring(0, Locale.getDefault().toString().indexOf("_"))
: Locale.getDefault().toString());
}
private static Class refineClass(final Object theAccessor, final Class theClass, final DataSource theSource, final Resource theId) {
Class aClass = theClass;
if (Collection.class.isAssignableFrom(aClass)) {
// if the field we're assigning from is a collection, try and figure out the type of the thing
// we're creating from the collection
Type[] aTypes = null;
if (theAccessor instanceof Field && ((Field)theAccessor).getGenericType() instanceof ParameterizedType) {
aTypes = ((ParameterizedType) ((Field)theAccessor).getGenericType()).getActualTypeArguments();
}
else if (theAccessor instanceof Method) {
aTypes = ((Method) theAccessor).getGenericParameterTypes();
}
if (aTypes != null && aTypes.length >= 1) {
// first type argument to a collection is usually the one we care most about
if (aTypes[0] instanceof ParameterizedType && ((ParameterizedType)aTypes[0]).getActualTypeArguments().length > 0) {
Type aType = ((ParameterizedType)aTypes[0]).getActualTypeArguments()[0];
if (aType instanceof Class) {
aClass = (Class) aType;
}
else if (aType instanceof WildcardTypeImpl) {
WildcardTypeImpl aWildcard = (WildcardTypeImpl) aType;
// trying to suss out super v extends w/o resorting to string munging.
if (aWildcard.getLowerBounds().length == 0 && aWildcard.getUpperBounds().length > 0) {
// no lower bounds afaik indicates ? extends Foo
aClass = ((Class)aWildcard.getUpperBounds()[0]);
}
else if (aWildcard.getLowerBounds().length > 0) {
// lower & upper bounds I believe indicates something of the form Foo super Bar
aClass = ((Class)aWildcard.getLowerBounds()[0]);
}
else {
// shoot, we'll try the string hack that Adrian posted on the mailing list.
try {
aClass = Class.forName(aType.toString().split(" ")[2].substring(0, aTypes[0].toString().split(" ")[2].length()-1));
}
catch (Exception e) {
// everything has failed, let aClass be the default (theClass) and hope for the best
}
}
}
else {
// punt? wtf else could it be?
try {
aClass = Class.forName(aType.toString());
}
catch (ClassNotFoundException e) {
// oh well, we did the best we can
}
}
}
else if (aTypes[0] instanceof Class) {
aClass = (Class) aTypes[0];
}
}
else {
// could not figure out the type from the generics assertions on the Collection, they are either
// not present, or my algorithm is not bullet proof. So lets try checking on the annotations
// for a type hint.
Class aTarget = BeanReflectUtil.getTargetEntity(theAccessor);
if (aTarget != null) {
aClass = aTarget;
}
}
}
if (!BeanReflectUtil.hasAnnotation(aClass, RdfsClass.class) || aClass.isInterface()) {
// k, so either the parameter of the collection or the declared type of the field does
// not map to an instance/bean type. this is most likely an error, but lets try and find
// the rdf:type of the field, and see if we can map that to a class in the path and we'll
// create an instance of that. that will work, and pushes the likely failure back off to
// the assignment of the created instance
Iterable<Resource> aTypes = DataSourceUtil.getTypes(theSource, theId);
// k, so now we know the type, if we can match the type to a class then we're in business
for (Resource aType : aTypes) {
if (aType instanceof URI) {
for (Class aTypeClass : TYPE_TO_CLASS.get( (URI) aType)) {
if ((BeanReflectUtil.hasAnnotation(aTypeClass, RdfsClass.class)) &&
(aClass.isAssignableFrom(aTypeClass))) {
// lets try this one
aClass = aTypeClass;
break;
}
}
}
}
}
if ( aClass.isInterface() ) {
if ( BeanReflectUtil.hasAnnotation( aClass, RdfsClass.class) ) {
URI aType = FACTORY.createURI( ((RdfsClass) aClass.getAnnotation( RdfsClass.class )).value() );
for (Class aTypeClass : TYPE_TO_CLASS.get(aType)) {
if ((BeanReflectUtil.hasAnnotation(aTypeClass, RdfsClass.class)) &&
(aClass.isAssignableFrom(aTypeClass))) {
// lets try this one
aClass = aTypeClass;
return aClass;
}
}
}
}
return aClass;
}
public static class ValueToObject implements Function<Value, Object> {
static final List<URI> integerTypes = Arrays.asList(XMLSchema.INT, XMLSchema.INTEGER, XMLSchema.POSITIVE_INTEGER,
XMLSchema.NEGATIVE_INTEGER, XMLSchema.NON_NEGATIVE_INTEGER,
XMLSchema.NON_POSITIVE_INTEGER, XMLSchema.UNSIGNED_INT);
static final List<URI> longTypes = Arrays.asList(XMLSchema.LONG, XMLSchema.UNSIGNED_LONG);
static final List<URI> floatTypes = Arrays.asList(XMLSchema.FLOAT, XMLSchema.DECIMAL);
static final List<URI> shortTypes = Arrays.asList(XMLSchema.SHORT, XMLSchema.UNSIGNED_SHORT);
static final List<URI> byteTypes = Arrays.asList(XMLSchema.BYTE, XMLSchema.UNSIGNED_BYTE);
private URI mProperty;
private Object mAccessor;
private DataSource mSource;
private Resource mResource;
public ValueToObject(final DataSource theSource, Resource theResource, final Object theAccessor, final URI theProp) {
mResource = theResource;
mSource = theSource;
mAccessor = theAccessor;
mProperty = theProp;
}
public Object apply(final Value theValue) {
if (mAccessor == null) {
throw new RuntimeException("Null accessor is not permitted");
}
if (theValue instanceof Literal) {
Literal aLit = (Literal) theValue;
URI aDatatype = aLit.getDatatype() != null ? aLit.getDatatype() : null;
if (aDatatype == null || XMLSchema.STRING.equals(aDatatype) || RDFS.LITERAL.equals(aDatatype)) {
return aLit.getLabel();
}
else if (XMLSchema.BOOLEAN.equals(aDatatype)) {
return Boolean.valueOf(aLit.getLabel());
}
else if (integerTypes.contains(aDatatype)) {
return Integer.parseInt(aLit.getLabel());
}
else if (longTypes.contains(aDatatype)) {
return Long.parseLong(aLit.getLabel());
}
else if (XMLSchema.DOUBLE.equals(aDatatype)) {
return Double.valueOf(aLit.getLabel());
}
else if (floatTypes.contains(aDatatype)) {
return Float.valueOf(aLit.getLabel());
}
else if (shortTypes.contains(aDatatype)) {
return Short.valueOf(aLit.getLabel());
}
else if (byteTypes.contains(aDatatype)) {
return Byte.valueOf(aLit.getLabel());
}
else if (XMLSchema.ANYURI.equals(aDatatype)) {
try {
return new java.net.URI(aLit.getLabel());
}
catch (URISyntaxException e) {
LOGGER.warn("URI syntax exception converting literal value which is not a valid URI {} ", aLit.getLabel());
return null;
}
}
else if (XMLSchema.DATE.equals(aDatatype) || XMLSchema.DATETIME.equals(aDatatype)) {
return Dates.asDate(aLit.getLabel());
}
else if (XMLSchema.TIME.equals(aDatatype)) {
return new Date(Long.parseLong(aLit.getLabel()));
}
else {
// no idea what this value is from its data type. if the field takes a string
// we'll just assign the plain string, otherwise its an error
if (BeanReflectUtil.classFrom(mAccessor).isAssignableFrom(String.class)) {
return aLit.getLabel();
}
else {
throw new RuntimeException("Unsupported or unknown literal datatype");
}
}
}
else if (theValue instanceof BNode) {
// TODO: this is not bulletproof, clean this up
BNode aBNode = (BNode) theValue;
// we need to figure out what type of bean this instance maps to.
Class<?> aClass = BeanReflectUtil.classFrom(mAccessor);
aClass = refineClass(mAccessor, aClass, mSource, aBNode);
if (Collection.class.isAssignableFrom(BeanReflectUtil.classFrom(mAccessor))) {
AccessibleObject aAccess = (AccessibleObject) mAccessor;
RdfProperty aPropAnnotation = aAccess.getAnnotation(RdfProperty.class);
// the field takes a collection, lets create a new instance of said collection, and hopefully the
// bnode is a list. this approach will only work if the property is a singleton value, eg
// :inst someProperty _:a where _:a is the head of a list. if you have another value _:b for
// some property on :inst, we don't have any way of figuring out which one you're talking about
// since bnode id references are not guaranteed to be stable in SPARQL, ie just because its id "a"
// in the result set, does not mean i can do another query for _:a and get the expected results.
// and you can't do a describe for the same reason.
try {
String aQuery = getBNodeConstructQuery(mSource, mResource, mProperty);
Graph aGraph = mSource.graphQuery(aQuery);
Optional<Resource> aPossibleListHead = Graphs.getResource(aGraph, mResource, mProperty);
if (aPossibleListHead.isPresent() && Graphs.isList(aGraph, aPossibleListHead.get())) {
List<Value> aList;
// getting the list is only safe the the query dialect supports stable bnode ids in the query language.
if (aPropAnnotation != null && aPropAnnotation.isList() && mSource.getQueryFactory().getDialect().supportsStableBnodeIds()) {
try {
aList = asList(mSource, aPossibleListHead.get());
}
catch (DataSourceException e) {
throw new RuntimeException(e);
}
}
else {
aList = new ArrayList<Value>(GraphUtil.getObjects(aGraph, mResource, mProperty));
}
//return new ToObjectFunction(mSource, null, (AccessibleObject) mAccessor, null).apply(aList);
Collection<Object> aValues = BeanReflectUtil.instantiateCollectionFromField(BeanReflectUtil.classFrom(aAccess));
for (Value aValue : aList) {
Object aListValue = null;
try {
aListValue = getProxyOrDbObject(mAccessor, aClass, aValue, mSource);
}
catch (Exception e) {
// we'll throw an error in a second...
}
if (aListValue == null) {
throw new RuntimeException("Error converting a list value: " + aValue + " -> " + aClass);
}
aValues.add(aListValue);
}
return aValues;
}
}
catch (QueryException e) {
throw new RuntimeException(e);
}
}
try {
return getProxyOrDbObject(mAccessor, aClass, aBNode, mSource);
}
catch (Exception e) {
if (EmpireOptions.STRICT_MODE) {
throw new RuntimeException(e);
}
else {
return null;
}
}
}
else if (theValue instanceof URI) {
URI aURI = (URI) theValue;
try {
// we need to figure out what type of bean this instance maps to.
Class<?> aClass = BeanReflectUtil.classFrom(mAccessor);
aClass = refineClass(mAccessor, aClass, mSource, aURI);
if (aClass.isAssignableFrom(java.net.URI.class)) {
return java.net.URI.create(aURI.toString());
}
else {
return getProxyOrDbObject(mAccessor, aClass, java.net.URI.create(aURI.toString()), mSource);
}
}
catch (Exception e) {
if (EmpireOptions.STRICT_MODE) {
throw new RuntimeException(e);
}
else {
LOGGER.warn("Problem applying value {}, {} ", e.toString(), e.getCause());
return null;
}
}
}
else {
if (EmpireOptions.STRICT_MODE) {
throw new RuntimeException("Unexpected Value type");
}
else {
LOGGER.warn("Problem applying value : Unexpected Value type");
return null;
}
}
}
}
private static List<Value> asList(DataSource theSource, Resource theRes) throws DataSourceException {
List<Value> aList = Lists.newArrayList();
Resource aListRes = theRes;
while (aListRes != null) {
Resource aFirst = (Resource) DataSourceUtil.getValue(theSource, aListRes, RDF.FIRST);
Resource aRest = (Resource) DataSourceUtil.getValue(theSource, aListRes, RDF.REST);
if (aFirst != null) {
aList.add(aFirst);
}
if (aRest == null || aRest.equals(RDF.NIL)) {
aListRes = null;
}
else {
aListRes = aRest;
}
}
return aList;
}
private static final MethodFilter METHOD_FILTER = new MethodFilter() {
public boolean isHandled(final Method theMethod) {
return !theMethod.getName().equals("finalize");
}
};
@SuppressWarnings("unchecked")
private static <T> T getProxyOrDbObject(Object theAccessor, Class<T> theClass, Object theKey, DataSource theSource) throws Exception {
if (BeanReflectUtil.isFetchTypeLazy(theAccessor)) {
Proxy<T> aProxy = new Proxy<T>(theClass, asPrimaryKey(theKey), theSource);
ProxyFactory aFactory = new ProxyFactory();
if (!theClass.isInterface()) {
aFactory.setSuperclass(theClass);
aFactory.setInterfaces(ObjectArrays.concat(theClass.getInterfaces(), EmpireGenerated.class));
} else {
aFactory.setInterfaces(ObjectArrays.concat(theClass, ObjectArrays.concat(theClass.getInterfaces(), EmpireGenerated.class)));
}
aFactory.setFilter(METHOD_FILTER);
final ProxyHandler<T> aHandler = new ProxyHandler<T>(aProxy);
Object aObj = aFactory.createClass(METHOD_FILTER).newInstance();
((ProxyObject) aObj).setHandler(aHandler);
return (T) aObj;
}
else {
return fromRdf(theClass, asPrimaryKey(theKey), theSource);
}
}
/**
* Javassist {@link MethodHandler} implementation for method proxying.
* @param <T> the proxy class type
*/
public static class ProxyHandler<T> implements MethodHandler {
/**
* The proxy object which wraps the instance being proxied.
*/
private Proxy<T> mProxy;
/**
* Create a new ProxyHandler
* @param theProxy the proxy object
*/
private ProxyHandler(final Proxy<T> theProxy) {
mProxy = theProxy;
}
public Proxy<T> getProxy() {
return mProxy;
}
/**
* Delegates the methods to the Proxy
* @inheritDoc
*/
public Object invoke(final Object theThis, final Method theMethod, final Method theProxyMethod, final Object[] theArgs) throws Throwable {
return theMethod.invoke(mProxy.value(), theArgs);
}
}
private static String getBNodeConstructQuery(DataSource theSource, Resource theRes, URI theProperty) {
Dialect aDialect = theSource.getQueryFactory().getDialect();
String aSerqlQuery = "construct * from {" + aDialect.asQueryString(theRes) + "} <" + theProperty.toString() + "> {o}, {o} po {oo}";
String aSparqlQuery = "CONSTRUCT { " + aDialect.asQueryString(theRes) + " <"+theProperty.toString()+"> ?o . ?o ?po ?oo } \n" +
"WHERE\n" +
"{ " + aDialect.asQueryString(theRes) + " <" + theProperty.toString() + "> ?o.\n" +
"?o ?po ?oo. }";
if (theSource.getQueryFactory().getDialect() instanceof SerqlDialect) {
return aSerqlQuery;
}
else {
// TODO: we're just assuming/hoping at this point that they support sparql. which
// will most likely be the case, but possibly not always.
return aSparqlQuery;
}
}
public static class AsValueFunction implements Function<Object, Value> {
private AccessibleObject mField;
private RdfProperty annotation;
public AsValueFunction() {
}
public AsValueFunction(final AccessibleObject theField) {
mField = theField;
annotation = mField == null ? null : mField.getAnnotation(RdfProperty.class);
}
public Value apply(final Object theIn) {
if (theIn == null) {
return null;
}
else if (!EmpireOptions.STRONG_TYPING && BeanReflectUtil.isPrimitive(theIn)) {
return FACTORY.createLiteral(theIn.toString());
}
else if (Boolean.class.isInstance(theIn)) {
return FACTORY.createLiteral(Boolean.class.cast(theIn).booleanValue());
}
else if (Integer.class.isInstance(theIn)) {
return FACTORY.createLiteral(Integer.class.cast(theIn).intValue());
}
else if (Long.class.isInstance(theIn)) {
return FACTORY.createLiteral(Long.class.cast(theIn).longValue());
}
else if (Short.class.isInstance(theIn)) {
return FACTORY.createLiteral(Short.class.cast(theIn).shortValue());
}
else if (Double.class.isInstance(theIn)) {
return FACTORY.createLiteral(Double.class.cast(theIn).doubleValue());
}
else if (Float.class.isInstance(theIn)) {
return FACTORY.createLiteral(Float.class.cast(theIn).floatValue());
}
else if (Date.class.isInstance(theIn)) {
return FACTORY.createLiteral(Dates.datetime(Date.class.cast(theIn)), XMLSchema.DATETIME);
}
else if (String.class.isInstance(theIn)) {
if (annotation != null && !annotation.language().equals("")) {
return FACTORY.createLiteral(String.class.cast(theIn), annotation.language());
}
else {
return FACTORY.createLiteral(String.class.cast(theIn), XMLSchema.STRING);
}
}
else if (Character.class.isInstance(theIn)) {
return FACTORY.createLiteral(Character.class.cast(theIn));
}
else if (java.net.URI.class.isInstance(theIn)) {
if (annotation != null && annotation.isXsdUri()) {
return FACTORY.createLiteral(theIn.toString(), XMLSchema.ANYURI);
}
else {
return FACTORY.createURI(theIn.toString());
}
}
else if (Value.class.isAssignableFrom(theIn.getClass())) {
return Value.class.cast(theIn);
}
else if (BeanReflectUtil.hasAnnotation(theIn.getClass(), RdfsClass.class)) {
try {
return id(theIn);
}
catch (InvalidRdfException e) {
throw new RuntimeException(e);
}
}
else if (theIn instanceof ProxyHandler) {
return this.apply( ((ProxyHandler)theIn).mProxy.value());
}
else {
try {
Field aProxy = theIn.getClass().getDeclaredField("handler");
return this.apply(((ProxyHandler)BeanReflectUtil.safeGet(aProxy, theIn)).mProxy.value());
}
catch (Exception e) {
throw new RuntimeException("Unknown type conversion: " + theIn.getClass() + " " + theIn + " " + mField);
}
}
}
}
private static class ContainsResourceValues implements Predicate<Value> {
public boolean apply(final Value theValue) {
return theValue instanceof Resource;
}
}
private static class LanguageFilter implements Predicate<Value> {
private String mLangCode;
private LanguageFilter(final String theLangCode) {
mLangCode = theLangCode;
}
public void setLangCode(final String theLangCode) {
mLangCode = theLangCode;
}
public boolean apply(final Value theValue) {
return theValue instanceof Literal && mLangCode.equals(((Literal)theValue).getLanguage());
}
}
}