/*
* Copyright (c) 2009-2011 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.util;
import com.clarkparsia.empire.annotation.RdfsClass;
import com.clarkparsia.empire.annotation.NamedGraph;
import com.clarkparsia.empire.annotation.SupportsRdfIdImpl;
import com.clarkparsia.empire.annotation.RdfGenerator;
import com.clarkparsia.empire.annotation.AnnotationChecker;
import com.clarkparsia.empire.SupportsRdfId;
import com.clarkparsia.empire.ds.DataSource;
import com.clarkparsia.empire.Empire;
import com.clarkparsia.empire.impl.serql.SerqlDialect;
import com.clarkparsia.empire.impl.sparql.SPARQLDialect;
import com.complexible.common.util.PrefixMapping;
import com.complexible.common.net.NetUtils;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import org.openrdf.model.Resource;
import org.openrdf.model.BNode;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.query.parser.ParsedTupleQuery;
import org.openrdf.queryrender.builder.QueryBuilder;
import org.openrdf.queryrender.builder.QueryBuilderFactory;
import org.openrdf.queryrender.serql.SeRQLQueryRenderer;
import org.openrdf.queryrender.sparql.SPARQLQueryRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.List;
import java.util.ArrayList;
/**
* <p>A collection of utility functions for Empire.</p>
*
* @author Michael Grove
*
* @since 0.6.1
* @version 0.7
*/
public final class EmpireUtil {
/**
* The logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(Empire.class.getName());
/**
* Hide the constructor, you can't create instances of this class
*/
private EmpireUtil() {
}
/**
* Return the RDF-izable support object's identifier. This will be either a URI or a BNode. All java URI/URL objects
* and anything whose toString() value is a valid URI will be returned as such. All other strings are assumed
* to be bnode identifiers.
* @param theSupport the support object
* @return it's identifier as a Sesame Resource
*/
public static Resource asResource(SupportsRdfId theSupport) {
if (theSupport.getRdfId() == null) {
return null;
}
else if (theSupport.getRdfId() instanceof SupportsRdfId.URIKey) {
return ValueFactoryImpl.getInstance().createURI( ((SupportsRdfId.URIKey) theSupport.getRdfId()).value().toASCIIString() );
}
else if (theSupport.getRdfId() instanceof SupportsRdfId.BNodeKey) {
return ValueFactoryImpl.getInstance().createBNode( ((SupportsRdfId.BNodeKey) theSupport.getRdfId()).value() );
}
else {
String aValue = theSupport.getRdfId().toString();
if (NetUtils.isURI(aValue)) {
return ValueFactoryImpl.getInstance().createURI(aValue);
}
else {
return ValueFactoryImpl.getInstance().createBNode(aValue);
}
}
}
/**
* Return the object as an instanceof {@link com.clarkparsia.empire.SupportsRdfId}
* @param theObj the object
* @return the object as SupportsRdfId
* @throws ClassCastException if the object is not a valid SupportsRdfId
* @see com.clarkparsia.empire.SupportsRdfId
*/
public static SupportsRdfId asSupportsRdfId(Object theObj) {
if (theObj instanceof SupportsRdfId.RdfKey) {
return new SupportsRdfIdImpl( (SupportsRdfId.RdfKey) theObj);
}
else if (theObj instanceof java.net.URI) {
return new SupportsRdfIdImpl(new SupportsRdfId.URIKey( (java.net.URI) theObj));
}
else if (theObj instanceof SupportsRdfId) {
return (SupportsRdfId) theObj;
}
else {
return new SupportsRdfIdImpl(asPrimaryKey(theObj));
}
}
/**
* Returns whether or not a NamedGraph context has been specified for the type of the specified instance.
* When a named graph is specified, all operations which mutate the data source will attempt to operate
* on the specified named graph.
* @param theObj the object to check
* @return true if it has a named graph specified, false otherwise
*/
public static boolean hasNamedGraphSpecified(Object theObj) {
NamedGraph aAnnotation = theObj.getClass().getAnnotation(NamedGraph.class);
return aAnnotation != null &&
(aAnnotation.type() == NamedGraph.NamedGraphType.Instance || (aAnnotation.type() == NamedGraph.NamedGraphType.Static
&& !aAnnotation.value().equals("")));
}
/**
* Returns the URI of the named graph that operations involving instances should be performed. If null is returned
* operations will be performed on the data source without a specified context.
* @param theObj the instance
* @return the URI of the instance's named graph, or null if there isn't one
* @throws java.net.URISyntaxException if the named graph specified (when the type is {@link com.clarkparsia.empire.annotation.NamedGraph.NamedGraphType#Static}) is not a valid URI
*/
public static java.net.URI getNamedGraph(Object theObj) {
if (!hasNamedGraphSpecified(theObj)) {
return null;
}
NamedGraph aAnnotation = theObj.getClass().getAnnotation(NamedGraph.class);
if (aAnnotation.type() == NamedGraph.NamedGraphType.Instance) {
SupportsRdfId aId = asSupportsRdfId(theObj);
try {
return asURI(aId);
}
catch (URISyntaxException e) {
LOGGER.warn("There was an error trying to get the instance-level named graph URI from an object. Its key is not a URI.", e);
return null;
}
}
else {
return URI.create(aAnnotation.value());
}
}
/**
* Return the SupportsRdfId key as a java.net.URI.
* @param theSupport the RDF-izable support class
* @return the id key as a java URI, or null if it cannot be converted to a URI.
* @throws URISyntaxException thrown if the value is not a valid URI.
*/
private static java.net.URI asURI(SupportsRdfId theSupport) throws URISyntaxException {
if (theSupport.getRdfId() == null) {
return null;
}
else if (theSupport.getRdfId() instanceof SupportsRdfId.URIKey) {
return ((SupportsRdfId.URIKey) theSupport.getRdfId()).value();
}
else {
String aValue = theSupport.getRdfId().toString();
if (NetUtils.isURI(aValue)) {
return URI.create(aValue);
}
}
return null;
}
/**
* Return all instances of the specified class in the EntityManager
* @param theManager the manager to query
* @param theClass the type of objects to query for
* @param <T> the type of objects returned
* @return the list of all objects of the given type in the EntityManager
*/
public static <T> List<T> all(EntityManager theManager, Class<T> theClass) {
List<T> aList = new ArrayList<T>();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Starting read all");
}
long start = System.currentTimeMillis();
if (!AnnotationChecker.isValid(theClass) || !(theManager.getDelegate() instanceof DataSource)) {
return aList;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Class valid check {} ms ", (System.currentTimeMillis() - start));
}
start = System.currentTimeMillis();
RdfsClass aClass = theClass.getAnnotation(RdfsClass.class);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Got annotation in {} ms ", (System.currentTimeMillis() - start));
}
start = System.currentTimeMillis();
// this init should be handled by the static block in RdfGenerator, but if there is no annotation index,
// or RdfGenerator has not been referenced, the namespace stuff will not have been initialized. so we'll
// call this here as a backup.
RdfGenerator.addNamespaces(theClass);
QueryBuilder<ParsedTupleQuery> aQuery = QueryBuilderFactory.select("result").distinct()
.group().atom("result", RDF.TYPE, ValueFactoryImpl.getInstance().createURI(PrefixMapping.GLOBAL.uri(aClass.value()))).closeGroup();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created query in {} ms ", (System.currentTimeMillis() - start));
}
start = System.currentTimeMillis();
String aQueryStr = null;
try {
DataSource aSource = (DataSource) theManager.getDelegate();
start = System.currentTimeMillis();
if (aSource.getQueryFactory().getDialect() instanceof SPARQLDialect) {
aQueryStr = new SPARQLQueryRenderer().render(aQuery.query());
}
else if (aSource.getQueryFactory().getDialect() instanceof SerqlDialect) {
aQueryStr = new SeRQLQueryRenderer().render(aQuery.query());
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Got query string in {} ms ", (System.currentTimeMillis() - start));
}
start = System.currentTimeMillis();
}
catch (Exception e) {
throw new PersistenceException(e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("EntityManager open : " + theManager.isOpen());
}
List aResults = theManager.createNativeQuery(aQueryStr, theClass).getResultList();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created native query in {} ms", (System.currentTimeMillis() - start));
}
start = System.currentTimeMillis();
for (Object aObj : aResults) {
try {
aList.add( theClass.cast(aObj));
}
catch (ClassCastException e) {
throw new PersistenceException(e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Added result item in {} ms ", (System.currentTimeMillis() - start));
}
start = System.currentTimeMillis();
}
return aList;
}
/**
* Returns the object as a primary key value. Generally, for an rdf database, the identifying key for a resource
* will be it's rdf:ID or rdf:nodeID. So the value must be a URI, for named resources, or a non-uri string, for
* nodeID.
* @param theObj the possible primary key
* @return the primary key as a RdfKey object
* @throws IllegalArgumentException thrown if the value is null, or if it is not a valid URI, or it cannot be turned into one
*/
public static SupportsRdfId.RdfKey asPrimaryKey(Object theObj) {
if (theObj == null) {
throw new IllegalArgumentException("null is not a valid primary key for an Entity");
}
try {
if (theObj instanceof URI) {
return new SupportsRdfId.URIKey((URI) theObj);
}
else if (theObj instanceof URL) {
return new SupportsRdfId.URIKey(((URL) theObj).toURI());
}
else if (theObj instanceof org.openrdf.model.URI) {
return new SupportsRdfId.URIKey( URI.create( ((org.openrdf.model.URI) theObj).stringValue()) );
}
else if (theObj instanceof org.openrdf.model.BNode) {
return new SupportsRdfId.BNodeKey( ((BNode) theObj).getID() );
}
else {
if (NetUtils.isURI(theObj.toString())) {
return new SupportsRdfId.URIKey(new URI(theObj.toString()));
}
else {
if (theObj.toString().matches("[a-zA-Z_0-9]{1}[a-zA-Z_\\-0-9]*")) {
return new SupportsRdfId.BNodeKey(theObj.toString());
}
else if (theObj.toString().matches("_:[a-zA-Z_0-9]{1}[a-zA-Z_\\-0-9]*")) {
return new SupportsRdfId.BNodeKey(theObj.toString().substring(2));
}
throw new IllegalArgumentException("'" + theObj + "' is not a valid primary key, it is not a URI or a valid BNode identifer");
}
}
}
catch (URISyntaxException e) {
throw new IllegalArgumentException(theObj + " is not a valid primary key, it is not a URI.", e);
}
}
}