/*
* 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.entityhub.model.sesame;
import static org.apache.commons.collections.PredicateUtils.instanceofPredicate;
import static org.apache.commons.collections.PredicateUtils.notPredicate;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.PredicateUtils;
import org.apache.commons.collections.Transformer;
import org.apache.stanbol.entityhub.servicesapi.defaults.DataTypeEnum;
import org.apache.stanbol.entityhub.servicesapi.model.Reference;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.model.Text;
import org.apache.stanbol.entityhub.servicesapi.model.UnsupportedTypeException;
import org.apache.stanbol.entityhub.servicesapi.util.ModelUtils;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.Model;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.vocabulary.XMLSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link Representation} implementation backed by a Sesame {@link Model}
* @author Rupert Westenthaler
*/
public class RdfRepresentation implements Representation, RdfWrapper {
private Logger log = LoggerFactory.getLogger(RdfRepresentation.class);
private URI subject;
private final Model model;
private final RdfValueFactory factory;
private final org.openrdf.model.ValueFactory sesameFactory;
/**
* Emits {@link Statement#getObject()}
*/
protected Transformer objectTransFormer = new Transformer() {
@Override
public Value transform(Object input) {
return ((Statement)input).getObject();
}
};
/**
* Creates a {@link Representation} for the parsed subject. Data will be
* added to the model.
* @param subject the subject
* @param model the model
* @param factory the factory
*/
protected RdfRepresentation(URI subject, Model model, RdfValueFactory factory){
this.subject = subject;
this.model = model;
this.factory = factory;
this.sesameFactory = factory.getSesameFactory();
}
@Override
public void add(String field, Object value) throws IllegalArgumentException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
if(value == null){
throw new IllegalArgumentException("NULL values are not supported by Representations");
}
URI property = sesameFactory.createURI(field);
Collection<Object> values = new ArrayList<Object>();
//process the parsed value with the Utility Method ->
// this converts Objects as defined in the specification
ModelUtils.checkValues(factory, value, values);
//We still need to implement support for specific types supported by this implementation
for (Object current : values){
if (current instanceof Value){ //native support for Sesame types!
addValue(property, (Value)current);
} else if (current instanceof RdfWrapper){
//for Sesame RDF wrapper we can directly use the Value
addValue(property, ((RdfWrapper) current).getValue());
} else if (current instanceof Reference){
addValue(property, sesameFactory.createURI(((Reference) current).getReference()));
} else if (current instanceof Text){
addValue(property, sesameFactory.createLiteral(
((Text)current).getText(), ((Text)current).getLanguage()));
} else { //else add an typed Literal!
addValue(property, createTypedLiteral(current));
}
}
}
/**
* Converts a Java object to a Sesame typed Literal
* @param value the java value
* @return the Sesame literal
* @throws IllegalArgumentException it the parsed object could not be
* converted to a Sesame typed literal
*/
private Literal createTypedLiteral(Object value){
final Literal literal;
if(value instanceof Number){
Number n = (Number)value;
if(value instanceof Integer){
literal = sesameFactory.createLiteral(n.intValue());
} else if(value instanceof Float){
literal = sesameFactory.createLiteral(n.floatValue());
} else if(value instanceof Long){
literal = sesameFactory.createLiteral(n.longValue());
} else if(value instanceof Double){
literal = sesameFactory.createLiteral(n.doubleValue());
} else if(value instanceof Short){
literal = sesameFactory.createLiteral(n.shortValue());
} else if(value instanceof Byte){
literal = sesameFactory.createLiteral(n.byteValue());
} else {
literal = null;
}
} else if(value instanceof Boolean){
literal = sesameFactory.createLiteral(((Boolean)value).booleanValue());
} else if(value instanceof Date){
literal = sesameFactory.createLiteral((Date)value);
} else if(value instanceof BigInteger){
literal = sesameFactory.createLiteral(value.toString(),
DataTypeEnum.Integer.getUri());
} else if(value instanceof BigDecimal){
literal = sesameFactory.createLiteral(value.toString(),
DataTypeEnum.Decimal.getUri());
} else if(value instanceof XMLGregorianCalendar){
literal = sesameFactory.createLiteral((XMLGregorianCalendar)value);
} else if(value instanceof Duration){
literal = sesameFactory.createLiteral(value.toString(),
XMLSchema.DURATION);
} else if(value instanceof String){ //String type literals
literal = sesameFactory.createLiteral(value.toString(),
XMLSchema.STRING);
} else {
literal = null;
}
if(literal == null){
throw new IllegalArgumentException("Unable to convert value '"
+ value + "' to a Sesame typed literal because the java type "
+ value.getClass().getName() + " can not be mapped to an "
+ "XML DataType.");
}
return literal;
}
@Override
public void addNaturalText(String field, String text, String... languages) throws IllegalArgumentException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
if(text == null){
throw new IllegalArgumentException("NULL values are not supported by Representations");
}
URI property = sesameFactory.createURI(field);
if(languages == null || languages.length == 0){
languages = new String []{null};
}
for(String language : languages){
Literal value = sesameFactory.createLiteral(text, language);
addValue(property, value);
}
}
@Override
public void addReference(String field, String reference) throws IllegalArgumentException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
if(reference == null){
throw new IllegalArgumentException("NULL values are not supported by Representations");
} else if (reference.isEmpty()) {
throw new IllegalArgumentException("References MUST NOT be empty!");
}
addValue(sesameFactory.createURI(field), sesameFactory.createURI(reference));
}
/**
* Adds a value to a property and handles a possible
* {@link RepositoryException} while doing so
* @param property
* @param value
* @throws IllegalStateException in case of a {@link RepositoryException}
* while adding the value.
*/
private void addValue(URI property, Value value) {
model.add(subject, property, value);
}
@Override
@SuppressWarnings("unchecked")
public Iterator<Object> get(String field) throws IllegalArgumentException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
URI property = sesameFactory.createURI(field);
return IteratorUtils.transformedIterator(
IteratorUtils.filteredIterator(
IteratorUtils.transformedIterator(
model.filter(subject, property, null).iterator(),
objectTransFormer), // get the object from the statement
notPredicate(instanceofPredicate(BNode.class))),
org.apache.stanbol.entityhub.model.sesame.ModelUtils.VALUE_TRANSFORMER); // transform the values
}
@Override
@SuppressWarnings("unchecked")
public <T> Iterator<T> get(String field, final Class<T> type) throws UnsupportedTypeException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
URI property = sesameFactory.createURI(field);
//filter for values that are compatible with the parsed type
Iterator<?> iterator = IteratorUtils.filteredIterator(
IteratorUtils.transformedIterator(
model.filter(subject, property, null).iterator(),
objectTransFormer), // get the object from the statement
new ValueTypeFilter<T>(type));
if(!Value.class.isAssignableFrom(type)){
//if the requested type is not a Sesame value, we need also to
//transform results
iterator = IteratorUtils.transformedIterator(
iterator, // the already filtered values
org.apache.stanbol.entityhub.model.sesame.ModelUtils.VALUE_TRANSFORMER); // need to be transformed
}
return (Iterator<T>)iterator;
}
@Override
@SuppressWarnings("unchecked")
public Iterator<Text> get(String field, String...languages) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
URI property = sesameFactory.createURI(field);
return IteratorUtils.transformedIterator(
IteratorUtils.transformedIterator(
IteratorUtils.filteredIterator(
IteratorUtils.transformedIterator(
model.filter(subject, property, null).iterator(),
objectTransFormer), // get the object from the statement
new ValueTypeFilter<Text>(languages)), //filter languages
org.apache.stanbol.entityhub.model.sesame.ModelUtils.STRING_LITERAL_TO_TEXT_TRANSFORMER), //transform strings to Text
org.apache.stanbol.entityhub.model.sesame.ModelUtils.VALUE_TRANSFORMER); //transform to Text instances
}
@Override
@SuppressWarnings("unchecked")
public Iterator<String> getFieldNames() {
return (Iterator<String>)IteratorUtils.transformedIterator(
model.predicates().iterator(), org.apache.stanbol.entityhub.model.sesame.ModelUtils.VALUR_TO_STRING_TRANSFORMER);
}
@Override
public Object getFirst(String field) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
Iterator<Object> it = get(field);
if(it.hasNext()){
return it.next();
} else {
return null;
}
}
@Override
public <T> T getFirst(String field, Class<T> type) throws UnsupportedTypeException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
Iterator<T> it = get(field,type);
if(it.hasNext()){
return it.next();
} else {
return null;
}
}
@Override
public Text getFirst(String field, String...languages) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
if(languages == null){
log.debug("NULL parsed as languages -> replacing with \"new String []{null}\"" +
" -> assuming a missing explicit cast to (String) in the var arg");
languages = new String []{null};
}
Iterator<Text> it = get(field,languages);
if(it.hasNext()){
return it.next();
} else {
return null;
}
}
@Override
public Reference getFirstReference(String field) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
Iterator<Reference> it = getReferences(field);
return it.hasNext()?it.next():null;
}
@Override
public String getId() {
return subject.stringValue();
}
@Override
@SuppressWarnings("unchecked")
public Iterator<Reference> getReferences(String field) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
URI property = sesameFactory.createURI(field);
return IteratorUtils.transformedIterator(
IteratorUtils.filteredIterator(
IteratorUtils.transformedIterator(
model.filter(subject, property, null).iterator(),
objectTransFormer), // get the object from the statement
new ValueTypeFilter<Reference>(Reference.class)), //filter references
org.apache.stanbol.entityhub.model.sesame.ModelUtils.VALUE_TRANSFORMER); //transform to Text instances
}
@Override
@SuppressWarnings("unchecked")
public Iterator<Text> getText(String field) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
URI property = sesameFactory.createURI(field);
return IteratorUtils.transformedIterator(
IteratorUtils.transformedIterator(
IteratorUtils.filteredIterator(
IteratorUtils.transformedIterator(
model.filter(subject, property, null).iterator(),
objectTransFormer), // get the object from the statement
new ValueTypeFilter<Text>(Text.class)), //filter plain literals
org.apache.stanbol.entityhub.model.sesame.ModelUtils.STRING_LITERAL_TO_TEXT_TRANSFORMER),
org.apache.stanbol.entityhub.model.sesame.ModelUtils.VALUE_TRANSFORMER); //transform to Text instances
}
@Override
public void remove(String field, Object parsedValue) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
if(parsedValue == null){
log.warn("NULL parsed as value in remove method for symbol "+getId()
+" and field "+field+" -> call ignored");
return;
}
URI property = sesameFactory.createURI(field);
Collection<Object> values = new ArrayList<Object>();
ModelUtils.checkValues(factory, parsedValue, values);
for(Object value : values){
if (value instanceof Value){ //native support for Sesame types!
removeValue(property, (Value)value);
} else if (value instanceof RdfWrapper){
//for Sesame RDF wrapper we can directly use the Value
removeValue(property, ((RdfWrapper) value).getValue());
} else if (value instanceof Reference){
removeValue(property, sesameFactory.createURI(((Reference) value).getReference()));
} else if (value instanceof Text){
removeValue(property, sesameFactory.createLiteral(
((Text)value).getText(), ((Text)value).getLanguage()));
} else { //else add an typed Literal!
removeValue(property, createTypedLiteral(value));
}
}
}
/**
* Removes the value from the parsed property
* @param property
* @param value
*/
private boolean removeValue(URI property, Value value){
if(value != null){
return model.remove(subject, property, value);
} else {
return false;
}
}
@Override
public void removeAll(String field) throws IllegalArgumentException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
model.remove(subject, sesameFactory.createURI(field), null);
}
@Override
public void removeAllNaturalText(String field, String... languages) throws IllegalArgumentException {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
ValueTypeFilter<Literal> vtf = new ValueTypeFilter<Literal>(languages);
Iterator<Statement> statements = model.filter(
subject, sesameFactory.createURI(field), null).iterator();
while(statements.hasNext()){
Statement statement = statements.next();
if(vtf.evaluate(statement.getObject())){
statements.remove();
}
}
}
public void removeNaturalText(String field, String value, String... languages) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
if(value == null){
log.warn("NULL parsed as value in remove method for symbol "+getId()+" and field "+field+" -> call ignored");
}
if(languages == null || languages.length == 0){ //null or no language
//need to be interpreted as default language
languages = new String []{null};
}
URI property = sesameFactory.createURI(field);
for(String language : languages){
removeValue(property, sesameFactory.createLiteral(value, language));
if(language == null){ //we need also to remove xsd:string labels
removeValue(property, sesameFactory.createLiteral(value, XMLSchema.STRING));
}
}
}
@Override
public void removeReference(String field, String reference) {
if(field == null){
throw new IllegalArgumentException("The parsed field MUST NOT be NULL");
} else if(field.isEmpty()){
throw new IllegalArgumentException("The parsed field MUST NOT be Empty");
}
if(reference == null){
log.warn("NULL parsed as value in remove method for symbol "+getId()+" and field "+field+" -> call ignored");
} else {
removeValue(sesameFactory.createURI(field), sesameFactory.createURI(reference));
}
}
@Override
public void set(String field, Object value) throws IllegalArgumentException {
removeAll(field);
if(value != null){
add(field,value);
}
}
@Override
public void setNaturalText(String field, String text, String...languages) {
removeAllNaturalText(field, languages);
if(text != null){
addNaturalText(field, text, languages);
}
}
@Override
public void setReference(String field, String reference) {
removeAll(field);
if(reference != null){
addReference(field, reference);
}
}
/**
* Getter for the Model used by this Representation <p>
* Note that this model might also contain triples with other subjects as
* the one used by this representation.
* @return the model used by this representation.
*/
public Model getModel() {
return model;
}
public URI getURI() {
return subject;
}
@Override
public Value getValue() {
return subject;
}
@Override
public int hashCode() {
return subject.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Representation &&
getId().equals(((Representation)obj).getId());
}
@Override
public String toString() {
return subject.toString();
}
}