/*
* 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.commons.ldpath.clerezza;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.Lock;
import org.apache.clerezza.commons.rdf.BlankNode;
import org.apache.clerezza.commons.rdf.Language;
import org.apache.clerezza.commons.rdf.Literal;
import org.apache.clerezza.commons.rdf.BlankNodeOrIRI;
import org.apache.clerezza.commons.rdf.RDFTerm;
import org.apache.clerezza.commons.rdf.Triple;
import org.apache.clerezza.commons.rdf.Graph;
import org.apache.clerezza.commons.rdf.IRI;
import org.apache.clerezza.commons.rdf.impl.utils.PlainLiteralImpl;
import org.apache.clerezza.commons.rdf.impl.utils.TypedLiteralImpl;
import org.apache.clerezza.rdf.core.LiteralFactory;
import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.marmotta.ldpath.api.backend.RDFBackend;
import org.apache.marmotta.ldpath.model.backend.AbstractBackend;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Clerezza based implementation of {@link RDFBackend} interface. This implementation uses the
* {@link RDFTerm} objects of Clerezza as processing unit RDFBackend.<p>
*
* For type conversions of {@link Literal}s the {@link LiteralFactory}
* of Clerezza is used. In case parsed nodes are not {@link Literal} the
* super implementations of {@link AbstractBackend} are called as such also
* support converting values based on the string representation.
*
* @author anil.sinaci
* @author Rupert Westenthaler
*/
public class ClerezzaBackend extends AbstractBackend<RDFTerm> implements RDFBackend<RDFTerm> {
private static final Logger logger = LoggerFactory.getLogger(ClerezzaBackend.class);
private static final Collection<IRI> STRING_DATATYPES = Collections.unmodifiableCollection(new HashSet<IRI>(Arrays.asList(
new IRI("http://www.w3.org/2001/XMLSchema#string"),
new IRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"),
null)));
/**
* Enumeration containing supported XSD dataTypes including <ul>
* <li> local name
* <li> uri string
* <li> {@link URI}
* <li> {@link IRI}
* </ul>
* {@link #toString()} returns the uri.
*/
public static enum XSD {
INTEGER,INT,SHORT,BYTE,LONG,DOUBLE,FLOAT,
ANY_URI("anyURI"),DATE_TIME("dateTime"),BOOLEAN,STRING;
static final String namespace = "http://www.w3.org/2001/XMLSchema#";
String localName;
String uriString;
URI uri;
IRI uriRef;
/**
* uses <code>{@link #name()}{@link String#toLowerCase() .toLoverCase()}
* </code> to generate the {@link #getLocalName()}
*/
private XSD() {
this(null);
}
/**
* Constructor that allows to parse the local name. if <code>null</code>
* it uses <code>{@link #name()}{@link String#toLowerCase() .toLoverCase()}
* </code> to generate the {@link #getLocalName() localName}
* @param localName the local name or <code>null</code> to use
* <code>{@link #name()}{@link String#toLowerCase() .toLoverCase()}
* </code>
*/
private XSD(String localName){
this.localName = localName != null ? localName : name().toLowerCase();
this.uriString = namespace+this.localName;
this.uri = URI.create(uriString);
this.uriRef = new IRI(uriString);
}
public String getLocalName(){
return localName;
}
public String getUri(){
return uriString;
}
public URI getURI(){
return uri;
}
public IRI getIRI(){
return uriRef;
}
@Override
public String toString() {
return uriString;
}
private static BidiMap xsdURI2IRI = new DualHashBidiMap();
static {
for(XSD type : XSD.values()){
xsdURI2IRI.put(type.getURI(), type.getIRI());
}
}
public static URI getXsdURI(IRI uri){
return (URI)xsdURI2IRI.getKey(uri);
}
public static IRI getXsdIRI(URI uri){
return (IRI)xsdURI2IRI.get(uri);
}
}
private Graph graph;
private static LiteralFactory lf = LiteralFactory.getInstance();
/**
* Allows sub-classes to create a instance and setting the {@link #graph}
* later on by using {@link #setGraph(Graph)}.
*/
protected ClerezzaBackend() {
}
/**
* Constructs a Clerezza {@link RDFBackend} by using the parsed {@link Graph}
* @param graph the {@link Graph}
* @throws IllegalArgumentException if <code>null</code> is parsed as graph.
*/
public ClerezzaBackend(Graph graph) {
if(graph == null){
throw new IllegalArgumentException("The parsed ImmutableGraph MUST NOT be NULL!");
}
this.graph = graph;
}
protected final Graph getGraph(){
return this.graph;
}
protected final void setGraph(Graph graph){
this.graph = graph;
}
@Override
public RDFTerm createLiteral(String content) {
return createLiteral(content,null,null);
}
@Override
public RDFTerm createLiteral(String content, Locale language, URI type) {
logger.debug("creating literal with content \"{}\", language {}, datatype {}",
new Object[] {content, language, type});
if (type == null) {
if(language == null){
return new PlainLiteralImpl(content);
} else {
return new PlainLiteralImpl(content, new Language(language.getLanguage()));
}
} else {
return new TypedLiteralImpl(content, XSD.getXsdIRI(type));
}
}
@Override
public RDFTerm createURI(String uriref) {
return new IRI(uriref);
}
@Override
public Double doubleValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return LiteralFactory.getInstance().createObject(Double.class, (Literal) resource);
} else {
return super.doubleValue(resource);
}
}
@Override
public Locale getLiteralLanguage(RDFTerm resource) {
if (resource instanceof Literal) {
Language lang = ((Literal) resource).getLanguage();
return lang != null ? new Locale(lang.toString()) : null;
} else {
throw new IllegalArgumentException("RDFTerm " + resource.toString() + " is not a PlainLiteral");
}
}
@Override
public URI getLiteralType(RDFTerm resource) {
if (resource instanceof Literal) {
IRI type = ((Literal) resource).getDataType();
return type != null ? XSD.getXsdURI(type) : null;
} else {
throw new IllegalArgumentException("Value " + resource.toString() + " is not a literal");
}
}
@Override
public boolean isBlank(RDFTerm resource) {
return resource instanceof BlankNode;
}
@Override
public boolean isLiteral(RDFTerm resource) {
return resource instanceof Literal;
}
@Override
public boolean isURI(RDFTerm resource) {
return resource instanceof IRI;
}
@Override
public Collection<RDFTerm> listObjects(RDFTerm subject, RDFTerm property) {
if (!(property instanceof IRI) ||
!(subject instanceof BlankNodeOrIRI)) {
throw new IllegalArgumentException("Subject needs to be a URI or blank node, property a URI node");
}
Collection<RDFTerm> result = new ArrayList<RDFTerm>();
Lock readLock = readLockGraph();
try {
Iterator<Triple> triples = graph.filter((BlankNodeOrIRI) subject, (IRI) property, null);
while (triples.hasNext()) {
result.add(triples.next().getObject());
}
} finally {
if(readLock != null){ //will be null if #graph is a read-only graph instance
readLock.unlock();
}
}
return result;
}
@Override
public Collection<RDFTerm> listSubjects(RDFTerm property, RDFTerm object) {
if (!(property instanceof IRI)) {
throw new IllegalArgumentException("Property needs to be a URI node");
}
Collection<RDFTerm> result = new ArrayList<RDFTerm>();
Lock readLock = readLockGraph();
try {
Iterator<Triple> triples = graph.filter(null, (IRI) property, object);
while (triples.hasNext()) {
result.add(triples.next().getSubject());
}
} finally {
if(readLock != null){ //will be null if #graph is a read-only graph instance
readLock.unlock();
}
}
return result;
}
@Override
public Long longValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(Long.class, (Literal) resource);
} else {
return super.longValue(resource);
}
}
@Override
public String stringValue(RDFTerm resource) {
if (resource instanceof IRI) {
return ((IRI) resource).getUnicodeString();
} else if (resource instanceof Literal) {
return ((Literal) resource).getLexicalForm();
} else { //BlankNode
return resource.toString();
}
}
@Override
public Boolean booleanValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(Boolean.class, (Literal) resource);
} else {
return super.booleanValue(resource);
}
}
@Override
public Date dateTimeValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(Date.class, (Literal) resource);
} else {
return super.dateTimeValue(resource);
}
}
@Override
public Date dateValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(Date.class, (Literal) resource);
} else {
return super.dateValue(resource);
}
}
@Override
public Date timeValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(Date.class, (Literal) resource);
} else {
return super.timeValue(resource);
}
}
@Override
public Float floatValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(Float.class, (Literal) resource);
} else {
return super.floatValue(resource);
}
}
@Override
public Integer intValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(Integer.class, (Literal) resource);
} else {
return super.intValue(resource);
}
}
@Override
public BigInteger integerValue(RDFTerm resource) {
if (isDataTypeLiteral(resource)) {
return lf.createObject(BigInteger.class, (Literal) resource);
} else {
return super.integerValue(resource);
}
}
private boolean isDataTypeLiteral(RDFTerm resource){
return resource instanceof Literal &&
!STRING_DATATYPES.contains(((Literal)resource).getDataType());
}
@Override
public BigDecimal decimalValue(RDFTerm resource) {
//currently there is no converter for BigDecimal in clerezza
//so as a workaround use the lexical form (as provided by the super
//implementation
return super.decimalValue(resource);
}
@Override
public boolean supportsThreading() {
return false;
}
@Override
public ThreadPoolExecutor getThreadPool() {
return null;
}
/**
* @return the readLock or <code>null</code>if no read lock is needed
*/
private Lock readLockGraph() {
final Lock readLock;
readLock = graph.getLock().readLock();
readLock.lock();
return readLock;
}
}