Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworked RDFEntity interface and added SemanticEntity #37

Merged
merged 14 commits into from
Aug 8, 2018
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,99 @@ m("type", rdf::baseURI()) = RDFResource( "<" + rdf::baseURI() + "Person>" );
```
> Please note the round brackets as c++ does not allow multiple arguments for the square-bracket-operator. For the single-argument version you may use any type: `m["foo"]` and `m("foo")` are interchangable.

### SemanticEntity

An alternative to the RDFPropertyMap is the SemanticEntity-class. Both achieve the same goal: They provide an RDF-Triple representation of its given values. But the actual implementation and usages differ quite a lot: The RDFPropertyMap stores the given values itself and associates them with string-keys. This means that you are free to add values as you please, from any source, but you need to know exactly the key and type of the value whenever you want to access it -- the compiler won't help you there, you lose static type safety. The SemanticEntity implements the other extreme: It does not store the values itself but keeps only references to variables, and thus has some restrictions on what can be stored. But since your data stays in your usual c++ member variables, you can work with them without any conversions.

####Implementation details

The SemanticEntity class provides the methods:

```c++
template<class T> void registerPropetry(const std::string& predicate, T& property);
template<class T> void registerProperty(const std::string& subject,
const std::string& predicate, T& property);

template<class T> void registerPropertyPlain(const std::string& predicate, T& property);
template<class T> void registerPropertyPlain(const std::string& subject,
const std::string& predicate, T& property);
```

Which can be used to register a variable and thus make it available as a triple. The value of the variable will always be presented in the "object" part of the triple. The "predicate" part of the triple needs to be specified, whereas the "subject" part is optional: If omitted, the ID of the SemanticEntity is used, and the subject will be `"<" + sempr::baseURI() + entity->id() + ">"`, e.g. `<sempr:SemanticEntity_42>`.

Since the variable is given by reference you must ensure that its lifetime exceeds/is coupled to the lifetime of the SemanticEntity. You should only register member variables of the SemanticEntity itself, and thus have to _derive_ from SemanticEntity, which is why the above methods were made _protected_.

> **TODO**: _In the future, methods to check the lifetime of the owner of the variable may be added in order to use the SemanticEntity in a compositional fashion, just like the RDFPropertyMap, without inheriting it, but those are not implemented yet._

Internally, for every registered property an instance of `RegisteredPropertyBase` is stored in a vector of the SemanticEntity. Those objects simply provide an interface to `virtual std::string object() const = 0;` which is provided by the (templated) derived classes `RegisteredProperty`. The `RegisteredProperty`s in turn have to major modifications:

1. They also provide a `bool isValid() const;` which is implemented differently for `std::shared_ptr`, in which case they return if the property/variable/member is not null. In case of non-shared-ptrs it always returns true. This method is used to check if a registered property should be skipped on iteration, as a nullptr is (IMHO) best represented by the absence of a triple.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect such a details description as code documentation not as general in the readme.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is well-documented, of course :D. Everything you read here is described in code-comments, too -- this is just a more structured explanation of how stuff works internally that can be read in one go.

Also, it is written beneath the header "Implementation details", so IMHO this is totally fine. 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

2. The RegisteredProperty-template takes as a second template argument another template referenced to as `ToString`. This can be used to switch between different modes: Currently implemented are the templates `sempr::rdf::traits::n3_string` and `sempr::rdf::traits::plain_string` which are used by the methods `SemanticEntity::registerProperty` and `SemanticEntity::registerPropertyPlain` respectively. The former converts the given value to N3 notation, e.g. `"42"^^<http://www.w3.org/2001/XMLSchema#int>`, whereas the latter converts the values to plain strings, e.g. `42`.

The currently supported datatypes may be a bit limited: The default definition for `n3_string` uses sopranos literal nodes for conversion, the default for `plain_string` utilizes `std::to_string`. Both have a specialization for shared pointers to types derived from `sempr::entity::Entity`, in which case the id of the entity is used to construct the rdf value. If you want to extend the capabilities of the SemanticEntity to your own types than this is your starting point: Implement your own conversion functions.

#### Usage

If you want to use the approach of the SemanticEntity to make certain member variables of your class visible as an RDF triple, do the following:

1. Implement your class as described in the previous sections, but derive from SemanticEntity.
2. In your constructor, use `registerProperty` or `registerPropertyPlain` to make a member variable visible.

For example (parts from the PropertyTestEntity):

```c++
// PropertyTestEntity.hpp

#include <sempr/entity/SemanticEntity.hpp>
#include <sempr/entity/Person.hpp>

namespace sempr { namespace entity {

#pragma db object
class PropertyTestEntity : public SemanticEntity {
SEMPR_ENTITY
public:
using Ptr = std::shared_ptr<PropertyTestEntity>;
PropertyTestEntity();

int intValue_;
float floatValue_;
double doubleValue_;
std::string stringValue_;
Person::Ptr entityValue_;
};

}}
```

```c++
// PropertyTestEntity.cpp
#include <PropertyTestEntity_odb.h>


namespace sempr { namespace entity {

SEMPR_ENTITY_SOURCE(PropertyTestEntity);


PropertyTestEntity::PropertyTestEntity()
: SemanticEntity(new core::IDGen<PropertyTestEntity>())
{
this->setDiscriminator<PropertyTestEntity>();

// register member variables
this->registerProperty("<http://foo.bar/someInt>", intValue_);
this->registerProperty("<http://foo.bar/someFloat>", floatValue_);
this->registerProperty("<http://foo.bar/someDouble>", doubleValue_);
this->registerProperty("<http://foo.bar/someString>", stringValue_);
this->registerProperty("<http://foo.bar/someEntity>", entityValue_);
}

}}
```

It's as easy as that. Since a SemanticEntity implements the RDFEntity-Interface, whenever you call `PropertyTestEntity::changed()` all processing modules listening for RDFEntitys are informed, too, and when iterating over the triples of your entity have access to the current values of the registered properties.

### Geometry
To store geometric data, SEMPR relies on [GDAL](http://www.gdal.org/), which implements a standard proposed by the _Open Geospatial Consortium (OGC)_. The class hierarchy of `OGRGeometry` is mirrored in entities with the `Geometry`-base-class. The entity-classes simply wrap a corresponding geometry: `entity::Point` contains an `OGRPoint*`etc.

Expand Down
22 changes: 22 additions & 0 deletions include/sempr/core/Utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@ namespace entity {
} /* storage */

namespace core {

template <typename T>
struct remove_shared_ptr {
typedef T type;
};

template <typename T>
struct remove_shared_ptr<std::shared_ptr<T> > {
typedef T type;
};

template <typename T>
struct is_shared_ptr {
static constexpr bool value = false;
};

template <typename T>
struct is_shared_ptr<std::shared_ptr<T> > {
static constexpr bool value = true;
};


/**
A helper struct for SFINAE-check if odb::object_traits<T>::base_type exists.
In c++17: std::void_t
Expand Down
4 changes: 2 additions & 2 deletions include/sempr/entity/RDFDocument.hpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#ifndef SEMPR_ENTITY_RDFDOCUMENT_HPP_
#define SEMPR_ENTITY_RDFDOCUMENT_HPP_

#include <sempr/entity/RDFEntity.hpp>
#include <sempr/entity/RDFVector.hpp>

namespace sempr { namespace entity {

/**
RDFDocument loads all triples from a file into itself.
*/
#pragma db object
class RDFDocument : public RDFEntity {
class RDFDocument : public RDFVector {
SEMPR_ENTITY
private:
friend class odb::access;
Expand Down
85 changes: 64 additions & 21 deletions include/sempr/entity/RDFEntity.hpp
Original file line number Diff line number Diff line change
@@ -1,43 +1,86 @@
#ifndef SEMPR_ENTITY_RDFENTITY_H_
#define SEMPR_ENTITY_RDFENTITY_H_
#ifndef SEMPR_ENTITY_RDFENTITY_HPP_
#define SEMPR_ENTITY_RDFENTITY_HPP_

#include <odb/core.hxx>
#include <sempr/core/RDF.hpp>
#include <sempr/entity/Entity.hpp>
#include <sempr/entity/Triple.hpp>

// #include <sempr/core/EventBroker.hpp>
#include <sempr/core/EntityEvent.hpp>

#include <vector>

namespace sempr { namespace entity {


class TripleIterator_impl {
friend class TripleIterator;
protected:
virtual ~TripleIterator_impl();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange naming with *_impl and it implements nothing its just an interface.
Also the child's are named in this way :-(

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that I want to return an iterator by value, and not a pointer to an iterator that points to some triples. The latter would force the user to write stuff like
(**it).subject; or *(*it)->subject and to delete the iterator in the end. If I'd just return a FooIterator, but the method signature in the RDFEntity class specifies a TripleIterator, the result would be a TripleIterator created from a FooIterator... not what I wanted. So my solution is to give the TripleIterator class a pointer to the actual implementation which has to be provided by the entity types that are RDF-iterable. The TripleIterator_impl is the interface for this implementation, which we need for the TripleIterator to work correctly (virtual methods...).

I agree that the naming of the actual implementations [...]_impl is a bit weird, but it simply follows the pattern. Do you have any suggestion how to improve this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I think the TripleIterator shall be renamed because its an abstract wrapper for the real iterators. It could be renamed to something like AbstractTripleIterator or TripleIteratorWrapper after these step the *_impl could be removed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I'm on it.


virtual const Triple operator * () const = 0;
virtual void operator ++ () = 0;
virtual bool operator == (const TripleIterator_impl& other) const = 0;
};

/**
Custom abstract iterator to hide implementation of the container.
This way a RDFEntity can use a vector or a map or whatever it likes, just by implementing
the TripleIterator_impl.

This class uses the pimpl-idiom: Every RDFEntity-subtype returns a TripleIterator, but with
different implementations. To create your own implement a subclass of TripleIterator_impl and
pass an instance of it to the ctor of the TripleIterator.

Although the class is called "[...]Iterator" it not really behaves like one.
Since we cannot assert that every container implementing this actually stores triples which
we could reference, it needs to return triples by value instead.
*/
class TripleIterator {
TripleIterator_impl* impl_;
public:
TripleIterator(TripleIterator_impl* impl);
~TripleIterator();

/**
"read-only" access: Creates a copy of the represented triple.
*/
const Triple operator * () const;

TripleIterator& operator ++ ();
bool operator == (const TripleIterator& other) const;
bool operator != (const TripleIterator& other) const;
};


/**
The RDFEntity is the interface for all entities that focus on providing rdf triples. It only
allows iteration over the (constant!) triples, nothing more. This way every module may read the
triples, but only modify them if they know how to exactly. (This was a major problem before,
when RDFPropertyMap inherited RDFVector, so through RDFVector the RDFPropertyMap could become
invalid!)
*/
#pragma db object
class RDFEntity : public Entity {
SEMPR_ENTITY
public:
using Ptr = std::shared_ptr<RDFEntity>;
// using Event = core::EntityEvent<RDFEntity>;
RDFEntity();
RDFEntity(const core::IDGenBase*);
virtual ~RDFEntity(){}
virtual ~RDFEntity(){};

/** copy the triples into a vector */
void getTriples(std::vector<Triple>& triples) const;
Triple& getTripleAt(const size_t& index);
bool addTriple(const Triple& triple);
bool removeTriple(const Triple& triple);
void removeTripleAt(const size_t& index);
void clear();
size_t size() const;
std::vector<Triple>::const_iterator begin() const;
std::vector<Triple>::const_iterator end() const;

/** iterate the triples */
virtual TripleIterator begin() const;
virtual TripleIterator end() const;

protected:
RDFEntity(const core::IDGenBase*);
private:
friend class odb::access;
std::vector<Triple> triples_;
RDFEntity();
};

}}

#endif /* end of include guard: SEMPR_ENTITY_RDFENTITY_H_ */
} /* entity */

} /* sempr */

#endif /* end of include guard: SEMPR_ENTITY_RDFENTITY_HPP_ */
79 changes: 23 additions & 56 deletions include/sempr/entity/RDFPropertyMap.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
#ifndef SEMPR_ENTITY_RDFPROPERTYMAP_HPP_
#define SEMPR_ENTITY_RDFPROPERTYMAP_HPP_

#include <sempr/entity/RDFEntity.hpp>
#include <sempr/entity/RDFVector.hpp>
#include <sempr/entity/RDFValue.hpp>
#include <Soprano/Soprano> // for conversion to rdf-literals.

namespace sempr { namespace entity {

class RDFValueProxy;
class RDFPropertyMap;

class RDFPropertyMapIterator_impl : public TripleIterator_impl {
friend class RDFPropertyMap;
std::map<std::string, RDFValue>::const_iterator it_;
const RDFPropertyMap* pmap_;

RDFPropertyMapIterator_impl(const RDFPropertyMap* pmap,
std::map<std::string, RDFValue>::const_iterator it);

const Triple operator * () const override;
void operator ++ () override;
bool operator == (const TripleIterator_impl& other) const override;
};

/**
The RDFPropertyMap is an extension of the simple RDFEntity. It provides
Expand Down Expand Up @@ -35,6 +48,8 @@ class RDFPropertyMap : public RDFEntity {

virtual ~RDFPropertyMap(){}

TripleIterator begin() const override;
TripleIterator end() const override;

/// checks if an entry for a given key exists, without creating one
bool hasProperty(const std::string& key);
Expand All @@ -49,8 +64,8 @@ class RDFPropertyMap : public RDFEntity {
that has been stored at the given key. As this accesses an internal map it automatically
creates a new entry if necessary.
*/
RDFValueProxy operator[](const std::string& key);
RDFValueProxy operator()(const std::string& key);
RDFValue& operator[](const std::string& key);
RDFValue& operator()(const std::string& key);

/**
Allows to set a baseURI that differs from the default. To read/write the same variable
Expand All @@ -60,66 +75,18 @@ class RDFPropertyMap : public RDFEntity {
// will _not_ overwrite the previous, except if "http://example.com" is the default
// baseURI.
*/
RDFValueProxy operator()(const std::string& key, const std::string& baseURI);
RDFValue& operator()(const std::string& key, const std::string& baseURI);

private:
RDFPropertyMap(){}
RDFPropertyMap();
friend class odb::access;
friend class RDFValueProxy;

#pragma db value
struct Container {
RDFValue value_; // the actual value
size_t vectorIndex_; // the position of the Triple-Entry in RDFEntity
};
friend class RDFPropertyMapIterator_impl;

std::map<std::string, Container> keyValueMap_;
std::map<std::string, RDFValue> keyValueMap_;
std::string subject_;
std::string baseURI_;
};

/** A proxy around a RDFPropertyMap pointing at a fixed element. This is used
to update the triples of the RDFPropertyMap after altering an RDFValue it is
pointing at!
*/
class RDFValueProxy {
// the property map the value resides in. a raw pointer is sufficient, the
// proxy is NOT intended to be stored - it is just a helper.
RDFPropertyMap* propertyMap_;
// defines the entry to point at
std::string key_;
public:
RDFValueProxy(RDFPropertyMap* map, const std::string key)
: propertyMap_(map), key_(key)
{
}

/**
Assign a value. This updates the RDFValue-Object (which actually
implments the conversion methods, using Soprano::LiteralValue etc.),
updates the associated "Triple" and emits a changed-event.
*/
template <typename T>
RDFValueProxy& operator = (const T& value) {
// update the RDFValue
propertyMap_->keyValueMap_[key_].value_ = value;
// update the corresponding triple
size_t index = propertyMap_->keyValueMap_[key_].vectorIndex_;
Triple& triple = propertyMap_->getTripleAt(index);
triple.object = propertyMap_->keyValueMap_[key_].value_.toString();
// emit a changed-event (?)
// TODO -- maybe not and leave it to the user?
propertyMap_->changed();
return *this;
}

/** Casts -- implemented by RDFValue */
template <typename T>
operator T() const {
return propertyMap_->keyValueMap_[key_].value_;
}
};



} /* entity */
Expand Down
Loading