GA-CCRi Analytical Development Services

Using Scala traits to avoid delegation

Scala lets us write short and expressive code, and today we’re going to look at one of the ways we’ve leveraged that ability in the GeoMesa codebase. In implementing the Geotools DataStore/FeatureSource/FeatureStore interfaces, we used Scala’s mix-ins and linearization instead of delegation to deal with a multiple inheritance issue.

In GeoTools,the FeatureSource interface provides read-only access to a collection of SimpleFeatures. The FeatureStore interface extends FeatureSource by adding methods which allow for modifying features and adding new ones to the collection. In order to help developers create new data sources for the GeoTools user base, the GeoTools community provides abstract implementations of these interfaces. (*Actually, they have two abstract implementations. We’ll see each in turn.)

[java] public abstract class ContentFeatureSource implements SimpleFeatureSource { … }

public abstract class ContentFeatureStore extends ContentFeatureSource
implements SimpleFeatureStore,
FeatureLocking<SimpleFeatureType, SimpleFeature> { … }
[/java]

As we’re writing the JDBCFeatureSource, we can write extend ContentFeatureSource and use their implementation to save us some work.

[java] public class JDBCFeatureSource extends ContentFeatureSource { … }
[/java]

Ok, now, it is time to move onto the JDBCFeatureStore. The problem is that we’d like to extend GT’s ContentFeatureStore to use their work as well extend our new JDBCFeatureSource.

(Unfortunately, we can’t extend along the dotted red line.)

(Unfortunately, we can’t extend along the dotted red line.)

Since Java does not allow us to inherit from multiple classes, we are left with adding a JDBCFeatureSource variable which we will use to delegate.   In order to reuse code in either the JDBCFeatureSource or in the ContentFeatureSource, we must add code calling methods on the delegate variable.  The result is lots of boilerplate.

[java] public final class JDBCFeatureStore extends ContentFeatureStore {
JDBCFeatureSource delegate;

@Override
public JDBCDataStore getDataStore() {
return delegate.getDataStore();
}

@Override
public ContentEntry getEntry() {
return delegate.getEntry();
}
….
}
[/java]

We are essentially discussing the “Diamond Problem“. We have two classes (JDBCFeatureSource and ContentFeatureStore) which both extend the same class (ContentFeatureSource) and we wish to write a class which extends them. In Java, while we can implement multiple interfaces, we cannot extend more than one class. As such, we have to rely on techniques like delegation. In Scala, we are allowed to mix-in multiple traits. (*The Scala compiler linearizes the mixed-in traits and their interactions can be interesting…but that’s a topic for another post. (Linearization of an Object’s Hierarchy))

Let’s look at the class hierarchy in GeoMesa. (Note that we use the other abstract implementations: AbstractFeatureSource and AbstractFeatureStore.)

(Black lines indicate that the class or trait extends the previous class. The yellow lines indicate a trait mix-in.)

(Black lines indicate that the class or trait extends the previous class. The yellow lines indicate a trait mix-in.)

Here we can win by creating a trait which contains the implementation details of our AccumuloFeatureSource.

[java] trait AccumuloAbstractFeatureSource extends AbstractFeatureSource { … }
[/java]

Since we’ve done the work for creating the AccumuloFeatureSource in our trait, our class AccumuloFeatureSource has a trivial implementation and we can mix-in its details into the AccumuloFeatureStore.

[java] class AccumuloFeatureSource(val dataStore: AccumuloDataStore, val featureName: String)
extends AccumuloAbstractFeatureSource

class AccumuloFeatureStore(val dataStore: AccumuloDataStore, val featureName: String)
extends AbstractFeatureStore with AccumuloAbstractFeatureSource {
override def addFeatures(collection: FeatureCollection[SimpleFeatureType,
SimpleFeature]): JList[FeatureId] = {
writeBounds(collection.getBounds)
super.addFeatures(collection)
}

def writeBounds(envelope: ReferencedEnvelope) {
if(envelope != null)
dataStore.writeBounds(featureName, envelope)
}
}
[/java]

Above we can see the code for our two classes and they are free of boilerplate. While
this example doesn’t dig deep into Scala type hierarchy, it does show how Scala can
allow us to write clean code which is easier to maintain. In the future, we also have
the flexibility to add security checks into the AccumuloDataStore’s getFeatureSource
method to allow only certain users access to the write capabilities of the
AccumuloFeatureStore.

Go Back