diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
index cbc107c7c80f2b603a6567238a252057df5cdd0a..e70d9fa81f10cf3afebdeded9373962e21c2e652 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
@@ -38,11 +38,9 @@ public class Listing extends NodeBase {
 	private boolean foundationMember;
 
 	@SortableField(name = "installs_count")
-	@JsonbProperty("installs_count")
 	private long installsTotal;
 
 	@SortableField(name = "installs_count_recent")
-	@JsonbProperty("installs_count_recent")
 	private long installsRecent;
 
 	@SortableField
@@ -64,7 +62,7 @@ public class Listing extends NodeBase {
 	private Organization organization;
 	private List<Author> authors;
 	private List<Tag> tags;
-	private List<SolutionVersion> versions;
+	private List<ListingVersion> versions;
 
 	/**
 	 * Default constructor, sets lists to empty lists to stop null pointers
@@ -180,6 +178,7 @@ public class Listing extends NodeBase {
 	/**
 	 * @return the installsTotal
 	 */
+	@JsonbProperty("installs_count")
 	public long getInstallsTotal() {
 		return installsTotal;
 	}
@@ -187,6 +186,7 @@ public class Listing extends NodeBase {
 	/**
 	 * @param installsTotal the installsTotal to set
 	 */
+	@JsonbTransient
 	public void setInstallsTotal(long installsTotal) {
 		this.installsTotal = installsTotal;
 	}
@@ -194,6 +194,7 @@ public class Listing extends NodeBase {
 	/**
 	 * @return the installsRecent
 	 */
+	@JsonbProperty("installs_count_recent")
 	public long getInstallsRecent() {
 		return installsRecent;
 	}
@@ -201,6 +202,7 @@ public class Listing extends NodeBase {
 	/**
 	 * @param installsRecent the installsRecent to set
 	 */
+	@JsonbTransient
 	public void setInstallsRecent(long installsRecent) {
 		this.installsRecent = installsRecent;
 	}
@@ -215,7 +217,6 @@ public class Listing extends NodeBase {
 	/**
 	 * @param favoriteCount the favoriteCount to set
 	 */
-	@JsonbTransient
 	public void setFavoriteCount(long favoriteCount) {
 		this.favoriteCount = favoriteCount;
 	}
@@ -368,14 +369,15 @@ public class Listing extends NodeBase {
 	/**
 	 * @return the versions
 	 */
-	public List<SolutionVersion> getVersions() {
+	public List<ListingVersion> getVersions() {
 		return new ArrayList<>(versions);
 	}
 
 	/**
 	 * @param versions the versions to set
 	 */
-	public void setVersions(List<SolutionVersion> versions) {
+	@JsonbTransient
+	public void setVersions(List<ListingVersion> versions) {
 		Objects.requireNonNull(versions);
 		this.versions = new ArrayList<>(versions);
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/SolutionVersion.java b/src/main/java/org/eclipsefoundation/marketplace/dto/ListingVersion.java
similarity index 77%
rename from src/main/java/org/eclipsefoundation/marketplace/dto/SolutionVersion.java
rename to src/main/java/org/eclipsefoundation/marketplace/dto/ListingVersion.java
index 1968ffbd451d21b5d21b284df9ec7a73a57d7a9c..24d06c97297de748e0c83bcb6c363776084940f8 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/SolutionVersion.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/ListingVersion.java
@@ -11,12 +11,14 @@ import java.util.List;
 import java.util.Objects;
 
 /**
- * Domain object representing a marketplace listing solution version
+ * Domain object representing a marketplace listing version
  * 
  * @author Martin Lowe
  */
-public class SolutionVersion {
+public class ListingVersion {
 
+	private String id;
+	private String listingId;
 	private String version;
 	private List<String> eclipseVersions;
 	private List<String> platforms;
@@ -24,12 +26,40 @@ public class SolutionVersion {
 	private String updateSiteUrl;
 	private List<FeatureId> featureIds;
 	
-	public SolutionVersion() {
+	public ListingVersion() {
 		this.eclipseVersions = new ArrayList<>();
 		this.platforms = new ArrayList<>();
 		this.featureIds = new ArrayList<>();
 	}
 
+	/**
+	 * @return the id
+	 */
+	public String getId() {
+		return id;
+	}
+
+	/**
+	 * @param id the id to set
+	 */
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	/**
+	 * @return the listingId
+	 */
+	public String getListingId() {
+		return listingId;
+	}
+
+	/**
+	 * @param listingId the listingId to set
+	 */
+	public void setListingId(String listingId) {
+		this.listingId = listingId;
+	}
+
 	/**
 	 * @return the versionString
 	 */
@@ -106,14 +136,15 @@ public class SolutionVersion {
 	 * @return the featureIds
 	 */
 	public List<FeatureId> getFeatureIds() {
-		return featureIds;
+		return new ArrayList<>(featureIds);
 	}
 
 	/**
 	 * @param featureIds the featureIds to set
 	 */
 	public void setFeatureIds(List<FeatureId> featureIds) {
-		this.featureIds = featureIds;
+		Objects.requireNonNull(featureIds);
+		this.featureIds = new ArrayList<>(featureIds);
 	}
 
 }
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
index 0ba97a394d8329c8c5fdd2ba1fd8ab942dc475c5..9265d119d3df81cc39c7fa914fd1204d7dff4901 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
@@ -23,7 +23,7 @@ import org.eclipsefoundation.marketplace.dto.Listing;
 import org.eclipsefoundation.marketplace.dto.converters.AuthorConverter;
 import org.eclipsefoundation.marketplace.dto.converters.CategoryConverter;
 import org.eclipsefoundation.marketplace.dto.converters.OrganizationConverter;
-import org.eclipsefoundation.marketplace.dto.converters.SolutionVersionConverter;
+import org.eclipsefoundation.marketplace.dto.converters.ListingVersionConverter;
 import org.eclipsefoundation.marketplace.dto.converters.TagConverter;
 import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 
@@ -43,7 +43,7 @@ public class ListingCodec implements CollectibleCodec<Listing> {
 	private final AuthorConverter authorConverter;
 	private final OrganizationConverter organizationConverter;
 	private final TagConverter tagConverter;
-	private final SolutionVersionConverter versionConverter;
+	private final ListingVersionConverter versionConverter;
 	private final CategoryConverter categoryConverter;
 
 	/**
@@ -55,7 +55,7 @@ public class ListingCodec implements CollectibleCodec<Listing> {
 		this.authorConverter = new AuthorConverter();
 		this.organizationConverter = new OrganizationConverter();
 		this.tagConverter = new TagConverter();
-		this.versionConverter = new SolutionVersionConverter();
+		this.versionConverter = new ListingVersionConverter();
 		this.categoryConverter = new CategoryConverter();
 	}
 
@@ -90,8 +90,6 @@ public class ListingCodec implements CollectibleCodec<Listing> {
 				value.getAuthors().stream().map(authorConverter::convert).collect(Collectors.toList()));
 		doc.put(DatabaseFieldNames.LISTING_TAGS,
 				value.getTags().stream().map(tagConverter::convert).collect(Collectors.toList()));
-		doc.put(DatabaseFieldNames.LISTING_VERSIONS,
-				value.getVersions().stream().map(versionConverter::convert).collect(Collectors.toList()));
 		documentCodec.encode(writer, doc, encoderContext);
 	}
 
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingVersionCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingVersionCodec.java
new file mode 100644
index 0000000000000000000000000000000000000000..2bda5745771fcd0d405e83e7f119fe3ec01362f6
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingVersionCodec.java
@@ -0,0 +1,81 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.dto.codecs;
+
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bson.BsonReader;
+import org.bson.BsonString;
+import org.bson.BsonValue;
+import org.bson.BsonWriter;
+import org.bson.Document;
+import org.bson.codecs.Codec;
+import org.bson.codecs.CollectibleCodec;
+import org.bson.codecs.DecoderContext;
+import org.bson.codecs.EncoderContext;
+import org.eclipsefoundation.marketplace.dto.ListingVersion;
+import org.eclipsefoundation.marketplace.dto.converters.ListingVersionConverter;
+
+import com.mongodb.MongoClient;
+
+
+/**
+ * MongoDB codec for transcoding of {@link ListingVersion} and {@link Document}
+ * objects. Used when writing or retrieving objects of given type from the
+ * database.
+ * 
+ * @author Martin Lowe
+ */
+public class ListingVersionCodec implements CollectibleCodec<ListingVersion> {
+	private final Codec<Document> documentCodec;
+
+	private ListingVersionConverter cc;
+
+	/**
+	 * Creates the codec and initializes the codecs and converters needed to create
+	 * a listing from end to end.
+	 */
+	public ListingVersionCodec() {
+		this.documentCodec = MongoClient.getDefaultCodecRegistry().get(Document.class);
+		this.cc = new ListingVersionConverter();
+	}
+	
+	@Override
+	public void encode(BsonWriter writer, ListingVersion value, EncoderContext encoderContext) {
+		documentCodec.encode(writer, cc.convert(value), encoderContext);
+	}
+
+	@Override
+	public Class<ListingVersion> getEncoderClass() {
+		return ListingVersion.class;
+	}
+
+	@Override
+	public ListingVersion decode(BsonReader reader, DecoderContext decoderContext) {
+		return cc.convert(documentCodec.decode(reader, decoderContext));
+	}
+
+	@Override
+	public ListingVersion generateIdIfAbsentFromDocument(ListingVersion document) {
+		if (!documentHasId(document)) {
+			document.setId(UUID.randomUUID().toString());
+		}
+		return document;
+	}
+
+	@Override
+	public boolean documentHasId(ListingVersion document) {
+		return !StringUtils.isBlank(document.getId());
+	}
+
+	@Override
+	public BsonValue getDocumentId(ListingVersion document) {
+		return new BsonString(document.getId());
+	}
+
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/converters/SolutionVersionConverter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/converters/ListingVersionConverter.java
similarity index 72%
rename from src/main/java/org/eclipsefoundation/marketplace/dto/converters/SolutionVersionConverter.java
rename to src/main/java/org/eclipsefoundation/marketplace/dto/converters/ListingVersionConverter.java
index 880f31717d1abfd2fa89374f7b4c5e8472402001..bdc24152004a328f878170c4851df5dc9452bc36 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/converters/SolutionVersionConverter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/converters/ListingVersionConverter.java
@@ -9,21 +9,23 @@ package org.eclipsefoundation.marketplace.dto.converters;
 import java.util.stream.Collectors;
 
 import org.bson.Document;
-import org.eclipsefoundation.marketplace.dto.SolutionVersion;
+import org.eclipsefoundation.marketplace.dto.ListingVersion;
 import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 
 /**
- * Converter implementation for the {@link SolutionVersion} object.
+ * Converter implementation for the {@link ListingVersion} object.
  * 
  * @author Martin Lowe
  */
-public class SolutionVersionConverter implements Converter<SolutionVersion> {
+public class ListingVersionConverter implements Converter<ListingVersion> {
 
 	private final FeatureIdConverter featureIdConverter = new FeatureIdConverter();
 
 	@Override
-	public SolutionVersion convert(Document src) {
-		SolutionVersion version = new SolutionVersion();
+	public ListingVersion convert(Document src) {
+		ListingVersion version = new ListingVersion();
+		version.setId(src.getString(DatabaseFieldNames.DOCID));
+		version.setListingId(src.getString(DatabaseFieldNames.LISTING_ID));
 		version.setEclipseVersions(src.getList("compatible_versions", String.class));
 		version.setPlatforms(src.getList("platforms", String.class));
 		version.setMinJavaVersion(src.getString("min_java_version"));
@@ -35,8 +37,10 @@ public class SolutionVersionConverter implements Converter<SolutionVersion> {
 	}
 
 	@Override
-	public Document convert(SolutionVersion src) {
+	public Document convert(ListingVersion src) {
 		Document doc = new Document();
+		doc.put(DatabaseFieldNames.DOCID, src.getId());
+		doc.put(DatabaseFieldNames.LISTING_ID, src.getListingId());
 		doc.put("compatible_versions", src.getEclipseVersions());
 		doc.put("platforms", src.getPlatforms());
 		doc.put("min_java_version", src.getMinJavaVersion());
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java
index 137bac5f2f5b9f29b3f8b8723ec060bbe9a01a4d..e2aaae11e759b477c0182a58d85366be0b16323a 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java
@@ -30,12 +30,15 @@ import com.mongodb.client.model.Filters;
 public class CatalogFilter implements DtoFilter<Catalog> {
 
 	@Override
-	public List<Bson> getFilters(RequestWrapper wrap) {
+	public List<Bson> getFilters(RequestWrapper wrap, String root) {
 		List<Bson> filters = new ArrayList<>();
-		// ID check
-		Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
-		if (id.isPresent()) {
-			filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+		// perform following checks only if there is no doc root
+		if (root == null) {
+			// ID check
+			Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
+			if (id.isPresent()) {
+				filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+			}
 		}
 		return filters;
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CategoryFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CategoryFilter.java
index 7bdf5620b4407978ea83ff2a141f898f206561e8..3092ecf53f48b7dbeaf00bbf31eb386a8e859034 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CategoryFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CategoryFilter.java
@@ -29,12 +29,15 @@ import com.mongodb.client.model.Filters;
 public class CategoryFilter implements DtoFilter<Category> {
 
 	@Override
-	public List<Bson> getFilters(RequestWrapper wrap) {
+	public List<Bson> getFilters(RequestWrapper wrap, String root) {
 		List<Bson> filters = new ArrayList<>();
-		// ID check
-		Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
-		if (id.isPresent()) {
-			filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+		// perform following checks only if there is no doc root
+		if (root == null) {
+			// ID check
+			Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
+			if (id.isPresent()) {
+				filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+			}
 		}
 		return filters;
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java
index e5c6d5c064ea6dea6d0da4a062362a08806fd89b..c642a7d6de5155e07c6d15df645dd7361f9d3c4b 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java
@@ -7,10 +7,14 @@
 package org.eclipsefoundation.marketplace.dto.filter;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.StringUtils;
 import org.bson.conversions.Bson;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
 
+import com.mongodb.client.model.Aggregates;
+
 /**
  * Filter interface for usage when querying data.
  * 
@@ -21,16 +25,19 @@ public interface DtoFilter<T> {
 	/**
 	 * Retrieve filter objects for the current arguments.
 	 * 
-	 * @param wrap wrapper for the current request
-	 * @return list of filters for the current request, or empty if there are no applicable filters.
+	 * @param wrap       wrapper for the current request
+	 * @param nestedPath current path for nesting of filters
+	 * @return list of filters for the current request, or empty if there are no
+	 *         applicable filters.
 	 */
-	List<Bson> getFilters(RequestWrapper wrap);
+	List<Bson> getFilters(RequestWrapper wrap, String nestedPath);
 
 	/**
 	 * Retrieve aggregate filter operations for the current arguments.
 	 * 
-	 * @param wrap wrapper for the current request
-	 * @return list of aggregates for the current request, or empty if there are no applicable aggregates.
+	 * @param wrap       wrapper for the current request
+	 * @return list of aggregates for the current request, or empty if there are no
+	 *         applicable aggregates.
 	 */
 	List<Bson> getAggregates(RequestWrapper wrap);
 
@@ -40,4 +47,28 @@ public interface DtoFilter<T> {
 	 * @return class of object to filter
 	 */
 	Class<T> getType();
+
+	/**
+	 * Wraps each of the filters present for a given filter type in an aggregate
+	 * match operation to port filter operations into an aggregate pipeline. This is
+	 * handy when importing nested types and enabling filters.
+	 * 
+	 * @param wrap       wrapper for the current request
+	 * @param nestedPath current path for nesting of filters
+	 * @return a list of aggregate pipeline operations representing the filters for
+	 *         the current request.
+	 */
+	default List<Bson> wrapFiltersToAggregate(RequestWrapper wrap, String nestedPath) {
+		return getFilters(wrap, nestedPath).stream().map(Aggregates::match).collect(Collectors.toList());
+	}
+
+	/**
+	 * 
+	 * @param root
+	 * @param fieldName
+	 * @return
+	 */
+	default String getPath(String root, String fieldName) {
+		return StringUtils.isBlank(root) ? fieldName : root + '.' + fieldName;
+	}
 }
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ErrorReportFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ErrorReportFilter.java
index 51a30bec9d03f77c8c4c979e3246949809343594..3f1ff92874463f8a42cb744b58941091db114cad 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ErrorReportFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ErrorReportFilter.java
@@ -30,7 +30,7 @@ import com.mongodb.client.model.Filters;
 public class ErrorReportFilter implements DtoFilter<ErrorReport> {
 
 	@Override
-	public List<Bson> getFilters(RequestWrapper wrap) {
+	public List<Bson> getFilters(RequestWrapper wrap, String root) {
 		List<Bson> filters = new ArrayList<>();
 
 		// ErrorReport ID check
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/InstallFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/InstallFilter.java
index baffe76b6748f5cea20afc688465e9818f229f14..29d1e8374bb39fce19f2e2ca169f22d39c94df07 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/InstallFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/InstallFilter.java
@@ -32,12 +32,15 @@ import com.mongodb.client.model.Filters;
 public class InstallFilter implements DtoFilter<Install> {
 
 	@Override
-	public List<Bson> getFilters(RequestWrapper wrap) {
+	public List<Bson> getFilters(RequestWrapper wrap, String root) {
 		List<Bson> filters = new ArrayList<>();
-		// Listing ID check
-		Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
-		if (id.isPresent()) {
-			filters.add(Filters.eq(DatabaseFieldNames.INSTALL_LISTING_ID, id.get()));
+		// perform following checks only if there is no doc root
+		if (root == null) {
+			// ID check
+			Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
+			if (id.isPresent()) {
+				filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+			}
 		}
 		// version check
 		Optional<String> version = wrap.getFirstParam(UrlParameterNames.VERSION);
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java
index c6d6d79183941fef5ba054f1c4d7787015a2c750..ceac34a0e0a45267c44246f530ea1257b9c8ea9f 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java
@@ -11,9 +11,11 @@ import java.util.List;
 import java.util.Optional;
 
 import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
 
 import org.bson.conversions.Bson;
 import org.eclipsefoundation.marketplace.dto.Listing;
+import org.eclipsefoundation.marketplace.dto.ListingVersion;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
 import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 import org.eclipsefoundation.marketplace.namespace.DtoTableNames;
@@ -23,35 +25,26 @@ import com.mongodb.client.model.Aggregates;
 import com.mongodb.client.model.Filters;
 
 /**
- * Filter implementation for the Listing class. Checks the following fields:
- * 
- * <ul>
- * <li>platform_version
- * <li>java_version
- * <li>os
- * <li>license_type
- * <li>q
- * <li>ids
- * <li>tags
- * </ul>
- * 
- * <p>
- * Injects categories into the results by way of aggregate pipeline.
- * </p>
+ * Filter implementation for the {@linkplain Listing} class.
  * 
  * @author Martin Lowe
  */
 @ApplicationScoped
 public class ListingFilter implements DtoFilter<Listing> {
 
+	@Inject
+	DtoFilter<ListingVersion> listingVersionFilter;
+	
 	@Override
-	public List<Bson> getFilters(RequestWrapper wrap) {
+	public List<Bson> getFilters(RequestWrapper wrap, String root) {
 		List<Bson> filters = new ArrayList<>();
-
-		// Listing ID check
-		Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
-		if (id.isPresent()) {
-			filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+		// perform following checks only if there is no doc root
+		if (root == null) {
+			// ID check
+			Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
+			if (id.isPresent()) {
+				filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+			}
 		}
 
 		// select by multiple IDs
@@ -66,29 +59,6 @@ public class ListingFilter implements DtoFilter<Listing> {
 			filters.add(Filters.eq(DatabaseFieldNames.LICENSE_TYPE, licType.get()));
 		}
 
-		// handle version sub document selection
-		List<Bson> versionFilters = new ArrayList<>();
-		// solution version - OS filter
-		Optional<String> os = wrap.getFirstParam(UrlParameterNames.OS);
-		if (os.isPresent()) {
-			versionFilters.add(Filters.eq("platforms", os.get()));
-		}
-		// solution version - eclipse version
-		Optional<String> eclipseVersion = wrap.getFirstParam(UrlParameterNames.ECLIPSE_VERSION);
-		if (eclipseVersion.isPresent()) {
-			versionFilters.add(Filters.eq("compatible_versions", eclipseVersion.get()));
-		}
-		// TODO this sorts by naturally by character rather than by actual number (e.g.
-		// 1.9 is technically greater than 1.10)
-		// solution version - Java version
-		Optional<String> javaVersion = wrap.getFirstParam(UrlParameterNames.JAVA_VERSION);
-		if (javaVersion.isPresent()) {
-			versionFilters.add(Filters.gte("min_java_version", javaVersion.get()));
-		}
-		if (!versionFilters.isEmpty()) {
-			filters.add(Filters.elemMatch("versions", Filters.and(versionFilters)));
-		}
-
 		// select by multiple tags
 		List<String> tags = wrap.getParams(UrlParameterNames.TAGS);
 		if (!tags.isEmpty()) {
@@ -107,6 +77,9 @@ public class ListingFilter implements DtoFilter<Listing> {
 	public List<Bson> getAggregates(RequestWrapper wrap) {
 		List<Bson> aggs = new ArrayList<>();
 		// adds a $lookup aggregate, joining categories on categoryIDS as "categories"
+		aggs.add(Aggregates.lookup(DtoTableNames.LISTING_VERSION.getTableName(), DatabaseFieldNames.DOCID, DatabaseFieldNames.LISTING_ID,
+				DatabaseFieldNames.LISTING_VERSIONS));
+		aggs.addAll(listingVersionFilter.wrapFiltersToAggregate(wrap, DatabaseFieldNames.LISTING_VERSIONS));
 		aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), DatabaseFieldNames.CATEGORY_IDS, DatabaseFieldNames.DOCID,
 				DatabaseFieldNames.LISTING_CATEGORIES));
 		List<String> marketIds = wrap.getParams(UrlParameterNames.MARKET_IDS);
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingVersionFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingVersionFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc0560f7b4aabfaa27eb45faa3e70fce70d44c40
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingVersionFilter.java
@@ -0,0 +1,76 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.dto.filter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.bson.conversions.Bson;
+import org.eclipsefoundation.marketplace.dto.ListingVersion;
+import org.eclipsefoundation.marketplace.model.RequestWrapper;
+import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
+import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
+
+import com.mongodb.client.model.Filters;
+
+/**
+ * Filter implementation for the {@linkplain ListingVersion} class.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@ApplicationScoped
+public class ListingVersionFilter implements DtoFilter<ListingVersion> {
+
+	@Override
+	public List<Bson> getFilters(RequestWrapper wrap, String root) {
+		List<Bson> filters = new ArrayList<>();
+		// perform following checks only if there is no doc root
+		if (root == null) {
+			// ID check
+			Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
+			if (id.isPresent()) {
+				filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+			}
+		}
+
+		// solution version - OS filter
+		Optional<String> os = wrap.getFirstParam(UrlParameterNames.OS);
+		if (os.isPresent()) {
+			filters.add(Filters.eq(getPath(root, "platforms"), os.get()));
+		}
+		// solution version - eclipse version
+		Optional<String> eclipseVersion = wrap.getFirstParam(UrlParameterNames.ECLIPSE_VERSION);
+		if (eclipseVersion.isPresent()) {
+			filters.add(Filters.eq(getPath(root, "compatible_versions"), eclipseVersion.get()));
+		}
+		// TODO this sorts by naturally by character rather than by actual number (e.g.
+		// 1.9 is technically greater than 1.10)
+		// solution version - Java version
+		Optional<String> javaVersion = wrap.getFirstParam(UrlParameterNames.JAVA_VERSION);
+		if (javaVersion.isPresent()) {
+			filters.add(Filters.gte(getPath(root, "min_java_version"), javaVersion.get()));
+		}
+		
+		return filters;
+	}
+
+	@Override
+	public List<Bson> getAggregates(RequestWrapper wrap) {
+		return Collections.emptyList();
+	}
+
+	@Override
+	public Class<ListingVersion> getType() {
+		return ListingVersion.class;
+	}
+
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
index a73cc5f620de58b3dac4852aacdf79f09817c4b8..2906257162df2b3e0cb88869933ad1f2cff62fe4 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
@@ -27,6 +27,7 @@ import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 
 import com.mongodb.client.model.Accumulators;
 import com.mongodb.client.model.Aggregates;
+import com.mongodb.client.model.Filters;
 import com.mongodb.client.model.Projections;
 import com.mongodb.client.model.Variable;
 
@@ -39,12 +40,15 @@ import com.mongodb.client.model.Variable;
 public class MarketFilter implements DtoFilter<Market> {
 
 	@Override
-	public List<Bson> getFilters(RequestWrapper wrap) {
+	public List<Bson> getFilters(RequestWrapper wrap, String root) {
 		List<Bson> filters = new ArrayList<>();
-		// ID check
-		Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
-		if (id.isPresent()) {
-			filters.add(eq(DatabaseFieldNames.DOCID, id.get()));
+		// perform following checks only if there is no doc root
+		if (root == null) {
+			// ID check
+			Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID);
+			if (id.isPresent()) {
+				filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get()));
+			}
 		}
 		return filters;
 	}
@@ -63,7 +67,7 @@ public class MarketFilter implements DtoFilter<Market> {
 				Projections.fields(Projections.excludeId(), Projections.include(DatabaseFieldNames.CATEGORY_IDS))));
 
 		// set up a var reference for the _id
-		Variable<String> id = new Variable<String>("market_id", "$" + DatabaseFieldNames.DOCID);
+		Variable<String> id = new Variable<>("market_id", "$" + DatabaseFieldNames.DOCID);
 		// lookup all category IDS from listings with the given market ID
 		aggs.add(Aggregates.lookup(DtoTableNames.LISTING.getTableName(), Arrays.asList(id), pipeline, tempFieldName));
 		// explode all category IDS for collection
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/providers/ListingVersionCodecProvider.java b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/ListingVersionCodecProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7b19327b4c98e0717519f14cb4e6f14449182dc
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/ListingVersionCodecProvider.java
@@ -0,0 +1,37 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.dto.providers;
+
+import org.bson.codecs.Codec;
+import org.bson.codecs.configuration.CodecProvider;
+import org.bson.codecs.configuration.CodecRegistry;
+import org.eclipsefoundation.marketplace.dto.Listing;
+import org.eclipsefoundation.marketplace.dto.ListingVersion;
+import org.eclipsefoundation.marketplace.dto.codecs.ListingCodec;
+import org.eclipsefoundation.marketplace.dto.codecs.ListingVersionCodec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides the {@link ListingCodec} to MongoDB for conversions of
+ * {@link Listing} objects.
+ * 
+ * @author Martin Lowe
+ */
+public class ListingVersionCodecProvider implements CodecProvider {
+	private static final Logger LOGGER = LoggerFactory.getLogger(ListingVersionCodecProvider.class);
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
+		if (clazz == ListingVersion.class) {
+			LOGGER.debug("Registering custom Listing class MongoDB codec");
+			return (Codec<T>) new ListingVersionCodec();
+		}
+		return null;
+	}
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java b/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java
index 4972ca632764105d12ca5bf5bc0ea6b9cdf49fe0..d792d51a0d4a9d27e785fc8f1b01fac02894296f 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java
@@ -17,7 +17,6 @@ import org.eclipsefoundation.marketplace.dto.filter.DtoFilter;
 import org.eclipsefoundation.marketplace.helper.SortableHelper;
 import org.eclipsefoundation.marketplace.helper.SortableHelper.Sortable;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
-import org.eclipsefoundation.marketplace.resource.AnnotationClassInjectionFilter;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,7 +49,6 @@ public class MongoQuery<T> {
 		this.dtoFilter = dtoFilter;
 		this.cache = cache;
 		this.aggregates = new ArrayList<>();
-
 		init();
 	}
 
@@ -68,7 +66,7 @@ public class MongoQuery<T> {
 
 		// get the filters for the current DTO
 		List<Bson> filters = new ArrayList<>();
-		filters.addAll(dtoFilter.getFilters(wrapper));
+		filters.addAll(dtoFilter.getFilters(wrapper, null));
 		
 		// get fields that make up the required fields to enable pagination and check
 		Optional<String> sortOpt = wrapper.getFirstParam(UrlParameterNames.SORT);
@@ -186,7 +184,7 @@ public class MongoQuery<T> {
 	 * @return the docType
 	 */
 	public Class<T> getDocType() {
-		return (Class<T>) wrapper.getAttribute(AnnotationClassInjectionFilter.ATTRIBUTE_NAME).get();
+		return dtoFilter.getType();
 	}
 
 	/**
diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/ResourceDataType.java b/src/main/java/org/eclipsefoundation/marketplace/model/ResourceDataType.java
deleted file mode 100644
index 99f725813595b21ce835b9f54c395e7627ab8e81..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/marketplace/model/ResourceDataType.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Copyright (c) 2019 Eclipse Foundation and others.
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Public License 2.0
- * which is available at http://www.eclipse.org/legal/epl-v20.html,
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipsefoundation.marketplace.model;
-
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * 
- * @author Martin Lowe
- */
-@Retention(RUNTIME)
-@Target(TYPE)
-public @interface ResourceDataType {
-
-	Class<?> value();
-}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java
index e03c9abcb63f785b607eb3c6c62698dd767cfd41..6c3042818388b1e63c5e86c950e4d9c26be70b99 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java
@@ -8,10 +8,11 @@ package org.eclipsefoundation.marketplace.namespace;
 
 import org.eclipsefoundation.marketplace.dto.Catalog;
 import org.eclipsefoundation.marketplace.dto.Category;
+import org.eclipsefoundation.marketplace.dto.ErrorReport;
 import org.eclipsefoundation.marketplace.dto.Install;
 import org.eclipsefoundation.marketplace.dto.Listing;
+import org.eclipsefoundation.marketplace.dto.ListingVersion;
 import org.eclipsefoundation.marketplace.dto.Market;
-import org.eclipsefoundation.marketplace.dto.ErrorReport;
 
 /**
  * Mapping of DTO classes to their respective tables in the DB.
@@ -20,10 +21,9 @@ import org.eclipsefoundation.marketplace.dto.ErrorReport;
  *
  */
 public enum DtoTableNames {
-	LISTING(Listing.class, "listings"),
-	CATEGORY(Category.class, "categories"),
-	CATALOG(Catalog.class, "catalogs"), MARKET(Market.class, "markets"),
-	ERRORREPORT(ErrorReport.class, "errorreports"), INSTALL(Install.class, "installs");
+	LISTING(Listing.class, "listings"), CATEGORY(Category.class, "categories"), CATALOG(Catalog.class, "catalogs"),
+	MARKET(Market.class, "markets"), ERRORREPORT(ErrorReport.class, "errorreports"), INSTALL(Install.class, "installs"),
+	LISTING_VERSION(ListingVersion.class, "listing_versions");
 
 	private Class<?> baseClass;
 	private String tableName;
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/AnnotationClassInjectionFilter.java b/src/main/java/org/eclipsefoundation/marketplace/resource/AnnotationClassInjectionFilter.java
deleted file mode 100644
index 3eaba5680a6a48159dbaf43fd7d2bc3bb6ebbf51..0000000000000000000000000000000000000000
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/AnnotationClassInjectionFilter.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright (c) 2019 Eclipse Foundation and others.
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Public License 2.0
- * which is available at http://www.eclipse.org/legal/epl-v20.html,
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.eclipsefoundation.marketplace.resource;
-
-import java.io.IOException;
-import java.util.List;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.container.ContainerRequestContext;
-import javax.ws.rs.container.ContainerRequestFilter;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.ext.Provider;
-
-import org.eclipsefoundation.marketplace.model.ResourceDataType;
-
-/**
- * Pre-processes the request to inject the datatype class name into the request.
- * This is later used in reflection to circumvent type erasure within Query
- * objects.
- * 
- * @author Martin Lowe
- */
-@Provider
-public class AnnotationClassInjectionFilter implements ContainerRequestFilter {
-	public static final String ATTRIBUTE_NAME = "enclosed-data-type";
-
-    @Context
-    HttpServletRequest request;
-    
-	@Override
-	public void filter(ContainerRequestContext requestContext) throws IOException {
-		List<Object> resources = requestContext.getUriInfo().getMatchedResources();
-		if (!resources.isEmpty()) {
-			// Quarkus compiles wrapper classes around beans, needs superclass call to get original class
-			Class<?> clazz = resources.get(0).getClass().getSuperclass();
-			// get the resource data type
-			ResourceDataType[] dataTypes = clazz.getAnnotationsByType(ResourceDataType.class);
-			if (dataTypes.length > 0) {
-				ResourceDataType firstType = dataTypes[0];
-				request.setAttribute(ATTRIBUTE_NAME, firstType.value());
-			}
-		}
-	}
-}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/CatalogResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/CatalogResource.java
index 369e3b159f4f2fac07df766356e4ee4ba67d6a95..49317fa2aacbdcdcafcd188736b5085e867711b4 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/CatalogResource.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/CatalogResource.java
@@ -29,7 +29,6 @@ import org.eclipsefoundation.marketplace.helper.StreamHelper;
 import org.eclipsefoundation.marketplace.model.Error;
 import org.eclipsefoundation.marketplace.model.MongoQuery;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
-import org.eclipsefoundation.marketplace.model.ResourceDataType;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.jboss.resteasy.annotations.jaxrs.PathParam;
@@ -42,7 +41,6 @@ import com.mongodb.client.result.DeleteResult;
  * @author martin
  *
  */
-@ResourceDataType(Catalog.class)
 @Path("/catalogs")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/CategoryResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/CategoryResource.java
index 1879cc06c3a4fe2ec2a7a1752beba99aff5536b1..5152615ec22d63959c7aa5f07a7b6e784d6659d3 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/CategoryResource.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/CategoryResource.java
@@ -29,7 +29,6 @@ import org.eclipsefoundation.marketplace.helper.StreamHelper;
 import org.eclipsefoundation.marketplace.model.Error;
 import org.eclipsefoundation.marketplace.model.MongoQuery;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
-import org.eclipsefoundation.marketplace.model.ResourceDataType;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.jboss.resteasy.annotations.jaxrs.PathParam;
@@ -42,7 +41,6 @@ import com.mongodb.client.result.DeleteResult;
  * @author martin
  *
  */
-@ResourceDataType(Category.class)
 @Path("/categories")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/ErrorReportResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/ErrorReportResource.java
index 0c67ec02b2a95170bd6509d212a19fe49a7bef65..1bcae4c5b0ee1f93a2bd75445e9361749e2fa4c6 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/ErrorReportResource.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/ErrorReportResource.java
@@ -26,7 +26,6 @@ import org.eclipsefoundation.marketplace.dto.filter.DtoFilter;
 import org.eclipsefoundation.marketplace.helper.StreamHelper;
 import org.eclipsefoundation.marketplace.model.MongoQuery;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
-import org.eclipsefoundation.marketplace.model.ResourceDataType;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.jboss.resteasy.annotations.jaxrs.PathParam;
@@ -40,7 +39,6 @@ import org.slf4j.LoggerFactory;
  */
 @RequestScoped
 @Path("/error")
-@ResourceDataType(ErrorReport.class)
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
 public class ErrorReportResource {
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/InstallResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/InstallResource.java
index d5882ed583adfbe0a0cae50fbf7e081859db3a5f..8fac56e98b8568cc52a4710aea8bcabea19347c5 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/InstallResource.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/InstallResource.java
@@ -29,7 +29,6 @@ import org.eclipsefoundation.marketplace.helper.StreamHelper;
 import org.eclipsefoundation.marketplace.model.Error;
 import org.eclipsefoundation.marketplace.model.MongoQuery;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
-import org.eclipsefoundation.marketplace.model.ResourceDataType;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.jboss.resteasy.annotations.jaxrs.PathParam;
@@ -42,7 +41,6 @@ import org.slf4j.LoggerFactory;
  * @author Martin Lowe
  */
 @RequestScoped
-@ResourceDataType(Install.class)
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
 @Path("/installs")
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java
index d30a477e3b300a9eb010b14170121788cae0f901..f295a05f398b4a314590f5c7d0847f3c127c951b 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java
@@ -32,7 +32,6 @@ import org.eclipsefoundation.marketplace.helper.StreamHelper;
 import org.eclipsefoundation.marketplace.model.Error;
 import org.eclipsefoundation.marketplace.model.MongoQuery;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
-import org.eclipsefoundation.marketplace.model.ResourceDataType;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.jboss.resteasy.annotations.jaxrs.PathParam;
@@ -46,7 +45,6 @@ import com.mongodb.client.result.DeleteResult;
  * 
  * @author Martin Lowe
  */
-@ResourceDataType(Listing.class)
 @Path("/listings")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/ListingVersionResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingVersionResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbd82c896110e612af408ea37551e20638220748
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingVersionResource.java
@@ -0,0 +1,139 @@
+/* Copyright (c) 2019 Eclipse Foundation and others.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License 2.0
+ * which is available at http://www.eclipse.org/legal/epl-v20.html,
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.marketplace.resource;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.eclipsefoundation.marketplace.dao.MongoDao;
+import org.eclipsefoundation.marketplace.dto.ListingVersion;
+import org.eclipsefoundation.marketplace.dto.filter.DtoFilter;
+import org.eclipsefoundation.marketplace.helper.StreamHelper;
+import org.eclipsefoundation.marketplace.model.Error;
+import org.eclipsefoundation.marketplace.model.MongoQuery;
+import org.eclipsefoundation.marketplace.model.RequestWrapper;
+import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
+import org.eclipsefoundation.marketplace.service.CachingService;
+import org.jboss.resteasy.annotations.jaxrs.PathParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.client.result.DeleteResult;
+
+/**
+ * Resource for retrieving {@linkplain ListingVersion}s from the MongoDB
+ * instance.
+ * 
+ * @author Martin Lowe
+ */
+@RequestScoped
+@Path("/listing_versions")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class ListingVersionResource {
+	private static final Logger LOGGER = LoggerFactory.getLogger(ListingVersionResource.class);
+
+	@Inject
+	MongoDao dao;
+	@Inject
+	CachingService<List<ListingVersion>> cachingService;
+	@Inject
+	RequestWrapper params;
+	@Inject
+	DtoFilter<ListingVersion> dtoFilter;
+
+	@GET
+	public Response select() {
+		MongoQuery<ListingVersion> q = new MongoQuery<>(params, dtoFilter, cachingService);
+		// retrieve the possible cached object
+		Optional<List<ListingVersion>> cachedResults = cachingService.get("all", params,
+				() -> StreamHelper.awaitCompletionStage(dao.get(q)));
+		if (!cachedResults.isPresent()) {
+			LOGGER.error("Error while retrieving cached ListingVersions");
+			return Response.serverError().build();
+		}
+
+		// return the results as a response
+		return Response.ok(cachedResults.get()).build();
+	}
+
+	/**
+	 * Endpoint for /ListingVersion/ to post a new ListingVersion to the persistence layer.
+	 * 
+	 * @param listingVersion the ListingVersion object to insert into the database.
+	 * @return response for the browser
+	 */
+	@PUT
+	public Response putListingVersion(ListingVersion listingVersion) {
+		MongoQuery<ListingVersion> q = new MongoQuery<>(params, dtoFilter, cachingService);
+		// add the object, and await the result
+		StreamHelper.awaitCompletionStage(dao.add(q, Arrays.asList(listingVersion)));
+
+		// return the results as a response
+		return Response.ok().build();
+	}
+
+	/**
+	 * Endpoint for /listingVersions/\<listingVersionId\> to retrieve a specific ListingVersion from the
+	 * database.
+	 * 
+	 * @param listingVersionId the ListingVersion ID
+	 * @return response for the browser
+	 */
+	@GET
+	@Path("/{listingVersionId}")
+	public Response select(@PathParam("listingVersionId") String listingVersionId) {
+		params.addParam(UrlParameterNames.ID, listingVersionId);
+
+		MongoQuery<ListingVersion> q = new MongoQuery<>(params, dtoFilter, cachingService);
+		// retrieve a cached version of the value for the current listing
+		Optional<List<ListingVersion>> cachedResults = cachingService.get(listingVersionId, params,
+				() -> StreamHelper.awaitCompletionStage(dao.get(q)));
+		if (!cachedResults.isPresent()) {
+			LOGGER.error("Error while retrieving cached listing for ID {}", listingVersionId);
+			return Response.serverError().build();
+		}
+
+		// return the results as a response
+		return Response.ok(cachedResults.get()).build();
+	}
+
+	/**
+	 * Endpoint for /listingVersions/\<listingVersionId\> to retrieve a specific ListingVersion from the
+	 * database.
+	 * 
+	 * @param listingVersionId the listingVersion ID
+	 * @return response for the browser
+	 */
+	@DELETE
+	@Path("/{listingVersionId}")
+	public Response delete(@PathParam("listingVersionId") String listingVersionId) {
+		params.addParam(UrlParameterNames.ID, listingVersionId);
+
+		MongoQuery<ListingVersion> q = new MongoQuery<>(params, dtoFilter, cachingService);
+		// delete the currently selected asset
+		DeleteResult result = StreamHelper.awaitCompletionStage(dao.delete(q));
+		if (result.getDeletedCount() == 0 || !result.wasAcknowledged()) {
+			return new Error(Status.NOT_FOUND, "Did not find an asset to delete for current call").asResponse();
+		}
+		// return the results as a response
+		return Response.ok().build();
+	}
+}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java
index 7fd751e3d302ecb7d0e13abca7a009022b279fcf..8229a7221f2ba56f85007eac169f144fe5aeaa32 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java
@@ -29,7 +29,6 @@ import org.eclipsefoundation.marketplace.helper.StreamHelper;
 import org.eclipsefoundation.marketplace.model.Error;
 import org.eclipsefoundation.marketplace.model.MongoQuery;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
-import org.eclipsefoundation.marketplace.model.ResourceDataType;
 import org.eclipsefoundation.marketplace.namespace.UrlParameterNames;
 import org.eclipsefoundation.marketplace.service.CachingService;
 import org.jboss.resteasy.annotations.jaxrs.PathParam;
@@ -42,7 +41,6 @@ import com.mongodb.client.result.DeleteResult;
  * @author martin
  *
  */
-@ResourceDataType(Market.class)
 @Path("/markets")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
diff --git a/src/main/node/index.js b/src/main/node/index.js
index e5ccda6eaf3c57d0eda4fd4220cae7ad7a1476d3..441bdecf9954b16025abc089995a6cf38ab059c5 100644
--- a/src/main/node/index.js
+++ b/src/main/node/index.js
@@ -1,6 +1,6 @@
 const axios = require('axios');
 const instance = axios.create({
-  timeout: 1000,
+  timeout: 2500,
   headers: {'User-Agent': 'mpc/0.0.0'}
 });
 const randomWords = require('random-words');
@@ -65,7 +65,7 @@ function splice(arr) {
   return out;
 }
 
-function createListing(count) {
+async function createListing(count) {
   if (count >= max) {
     return;
   }
@@ -73,14 +73,25 @@ function createListing(count) {
   console.log(`Generating listing ${count} of ${max}`);
   var json = generateJSON(uuid.v4());
   instance.put(argv.s+"/listings/", json)
-    .then(() => {
-      var installs = Math.floor(Math.random()*argv.i);
-      console.log(`Generating ${installs} install records for listing '${json.id}'`);
-      createInstall(0, installs, json, () => createListing(count+1));
-    })
+    .then(listingCallback(json, count))
     .catch(err => console.log(err));
 }
 
+async function listingCallback(json, count) {
+  var installs = Math.floor(Math.random()*argv.i);
+  var solsCount = Math.floor(Math.random()*5) + 1;
+  var versions = [];
+  for (var j=0;j<solsCount;j++) {
+    var v = await createVersion(j, solsCount, json.id);
+    if (v != null) {
+      versions.push(v);
+    }
+  }
+  
+  console.log(`Generating ${installs} install records for listing '${json.id}'`);
+  createInstall(0, installs, json, versions, () => createListing(count+1));
+}
+
 function createCategory(count) {
   if (count >= categoryIds.length) {
     return;
@@ -101,28 +112,27 @@ function createMarket(count) {
     .catch(err => console.log(err));
 }
 
-function createInstall(curr, max, listing, callback) {
+function createInstall(curr, max, listing, versions, callback) {
   if (curr >= max) {
     return callback();
   }
-  var json = generateInstallJSON(listing);
+  var json = generateInstallJSON(listing, versions);
   instance.post(`${argv.s}/installs/${json['listing_id']}/${json.version}`, json)
-    .then(createInstall(curr+1,max,listing,callback))
+    .then(createInstall(curr+1,max,listing,versions,callback))
     .catch(err => console.log(err));
 }
 
-function generateJSON(id) {
-  var solutions = [];
-  var solsCount = Math.floor(Math.random()*5) + 1;
-  for (var i=0; i < solsCount; i++) {
-    solutions.push({
-      "version": i,
-      "eclipse_versions": splice(eclipseVs),
-      "min_java_version": javaVs[Math.floor(Math.random()*javaVs.length)],
-      "platforms": splice(platforms)
-    });
+async function createVersion(curr, max, id) {
+  if (curr >= max) {
+    return;
   }
-  
+  var json = generateVersionJSON(curr, id);
+  return instance.put(`${argv.s}/listing_versions`, json)
+    .then(() => {return json})
+    .catch(err => console.log(err));
+}
+
+function generateJSON(id) {
   return {
     "id": id,
   	"title": "Sample",
@@ -150,7 +160,6 @@ function generateJSON(id) {
   			"url": ""
   		}
   	],
-  	"versions": solutions,
 	"market_ids": splice(marketIds).splice(0,Math.ceil(Math.random()*2)),
   "category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5)+1),
 	"screenshots": ["http://www.example.com/img/sample.png"]
@@ -160,7 +169,7 @@ function generateJSON(id) {
 function generateCategoryJSON(id) {
   return {
     "id": id,
-    "name": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0],
+    "title": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0],
     "url": "https://www.eclipse.org"
   };
 }
@@ -168,13 +177,13 @@ function generateCategoryJSON(id) {
 function generateMarketJSON(id) {
   return {
     "id": id,
-    "name": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0],
+    "title": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0],
     "url": "https://www.eclipse.org"
   };
 }
 
-function generateInstallJSON(listing) {
-  var version = listing.versions[Math.floor(Math.random()*listing.versions.length)];
+function generateInstallJSON(listing,versions) {
+  var version = versions[Math.floor(Math.random()*versions.length)];
   var javaVersions = Array.from(javaVs).splice(javaVs.indexOf(version["min_java_version"]));
   var eclipseVersions = Array.from(eclipseVs).splice(eclipseVs.indexOf(version["eclipse_version"]));
   
@@ -186,3 +195,13 @@ function generateInstallJSON(listing) {
     "eclipse_version": shuff(eclipseVersions)[0]
   };
 }
+
+function generateVersionJSON(name, listingId) {
+  return {
+    "version": name,
+    "listing_id": listingId,
+    "eclipse_versions": splice(eclipseVs),
+    "min_java_version": javaVs[Math.floor(Math.random()*javaVs.length)],
+    "platforms": splice(platforms)
+  };
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 362434433bec5a6ffb1267224529113db6b3d082..60e41ba7bf6c1d7214d70575c4c27b947176dad2 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -7,7 +7,7 @@ quarkus.log.file.path=/tmp/logs/quarkus.log
 quarkus.mongodb.connection-string = mongodb://localhost:27017
 quarkus.mongodb.credentials.username=root
 quarkus.mongodb.write-concern.safe=true
-quarkus.mongodb.min-pool-size=10
+quarkus.mongodb.min-pool-size=100
 quarkus.mongodb.max-pool-size=1000
 quarkus.mongodb.write-concern.retry-writes=true
 mongodb.database=mpc